Updated on 2026-08-14
This commit is contained in:
commit
c96350c53a
30 changed files with 604 additions and 37 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.application)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
|
|
@ -25,6 +27,23 @@ android {
|
|||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
signingConfigs {
|
||||
getByName("debug") {
|
||||
val keystorePath = "src/main/assets/tangem-app-config/android/keystore/debug_keystore"
|
||||
val propertiesPath = "src/main/assets/tangem-app-config/android/keystore/debug_keystore.properties"
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
file(propertiesPath)
|
||||
.inputStream()
|
||||
.use { keystoreProperties.load(it) }
|
||||
|
||||
storeFile = file(keystorePath)
|
||||
storePassword = keystoreProperties["store_password"] as String
|
||||
keyAlias = keystoreProperties["key_alias"] as String
|
||||
keyPassword = keystoreProperties["key_password"] as String
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit e91a2992857afa5cefce05c2750daf175b783603
|
||||
Subproject commit c36c77faf2101b30f472b2d5ba00dc0b48af9baa
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DefaultVisaOTPStorage @Inject constructor(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : VisaOTPStorage {
|
||||
|
||||
private val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_otp_storage",
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun saveOTP(cardId: String, otp: ByteArray) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(otp, VISA_OTP_KEY_PREFIX + cardId)
|
||||
}
|
||||
|
||||
override suspend fun getOTP(cardId: String): ByteArray? = withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(VISA_OTP_KEY_PREFIX + cardId)
|
||||
}
|
||||
|
||||
override suspend fun removeOTP(cardId: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(VISA_OTP_KEY_PREFIX + cardId)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val VISA_OTP_KEY_PREFIX = "visa_otp_"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -24,6 +25,7 @@ internal class TangemSdkManagerModule {
|
|||
@ApplicationContext context: Context,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
visaCardScanHandler: VisaCardScanHandler,
|
||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
): TangemSdkManager {
|
||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
MockTangemSdkManager(resources = context.resources)
|
||||
|
|
@ -32,6 +34,7 @@ internal class TangemSdkManagerModule {
|
|||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
resources = context.resources,
|
||||
visaCardScanHandler = visaCardScanHandler,
|
||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||
import com.tangem.tap.data.DefaultVisaAuthTokenStorage
|
||||
import com.tangem.tap.data.DefaultVisaOTPStorage
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,4 +17,8 @@ internal interface VisaStorageModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindVisaStorage(impl: DefaultVisaAuthTokenStorage): VisaAuthTokenStorage
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage
|
||||
}
|
||||
|
|
@ -24,6 +24,9 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
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.wallets.models.UserWalletId
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
|
|
@ -40,6 +43,7 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
|||
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.twins.CreateFirstTwinWalletTask
|
||||
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
|
||||
import com.tangem.tap.domain.twins.FinalizeTwinTask
|
||||
|
|
@ -55,6 +59,7 @@ internal class DefaultTangemSdkManager(
|
|||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val resources: Resources,
|
||||
private val visaCardScanHandler: VisaCardScanHandler,
|
||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
) : TangemSdkManager {
|
||||
|
||||
private val awaitInitializationMutex = Mutex()
|
||||
|
|
@ -466,6 +471,28 @@ internal class DefaultTangemSdkManager(
|
|||
|
||||
// endregion
|
||||
|
||||
// region Visa-specific
|
||||
|
||||
override suspend fun activateVisaCard(
|
||||
accessCode: String,
|
||||
challengeToSign: VisaAuthChallenge.Card?,
|
||||
activationInput: VisaActivationInput,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
return coroutineScope {
|
||||
runTaskAsyncReturnOnMain(
|
||||
runnable = visaCardActivationTaskFactory.create(
|
||||
accessCode = accessCode,
|
||||
challengeToSign = challengeToSign,
|
||||
activationInput = activationInput,
|
||||
coroutineScope = this,
|
||||
),
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
private const val MAX_INITIALIZE_ATTEMPTS = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
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.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaCardActivationResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
|
|
@ -194,4 +197,14 @@ class MockTangemSdkManager(
|
|||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Visa-specific
|
||||
|
||||
override suspend fun activateVisaCard(
|
||||
accessCode: String,
|
||||
challengeToSign: VisaAuthChallenge.Card?,
|
||||
activationInput: VisaActivationInput,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.reown.util.hexToBytes
|
||||
import com.tangem.common.CompletionResult
|
||||
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.toHexString
|
||||
import com.tangem.common.map
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||
import com.tangem.domain.common.visa.VisaUtilities
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
import com.tangem.operations.GenerateOTPCommand
|
||||
import com.tangem.operations.attestation.AttestCardKeyCommand
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.operations.sign.SignHashResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class VisaCardActivationTask @AssistedInject constructor(
|
||||
@Assisted private val accessCode: String,
|
||||
@Assisted private val challengeToSign: VisaAuthChallenge.Card?,
|
||||
@Assisted private val activationInput: VisaActivationInput,
|
||||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
private val otpStorage: VisaOTPStorage,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaActivationRepository: VisaActivationRepository,
|
||||
) : CardSessionRunnable<VisaCardActivationResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<VisaCardActivationResponse>) {
|
||||
coroutineScope.launch {
|
||||
callback(runSuspend(session))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runSuspend(session: CardSession): CompletionResult<VisaCardActivationResponse> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
if (card.cardId != activationInput.cardId) {
|
||||
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message))
|
||||
}
|
||||
|
||||
return if (challengeToSign != null) {
|
||||
signAuthorizationChallenge(session, challengeToSign)
|
||||
} else {
|
||||
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
||||
.getOrElse {
|
||||
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||
}
|
||||
|
||||
signOrder(session, activationOrder)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun signAuthorizationChallenge(
|
||||
session: CardSession,
|
||||
challengeToSign: VisaAuthChallenge.Card,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val attestationCommand = AttestCardKeyCommand(challenge = challengeToSign.challenge.hexToBytes())
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
attestationCommand.run(session = session) { attestationResponse ->
|
||||
continuation.resume(attestationResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
processSignedAuthorizationChallenge(
|
||||
session = session,
|
||||
signedChallenge = challengeToSign.toSignedChallenge(
|
||||
signedChallenge = result.data.cardSignature.toHexString(),
|
||||
salt = result.data.salt.toHexString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processSignedAuthorizationChallenge(
|
||||
session: CardSession,
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
return coroutineScope {
|
||||
val activationOrderDeferred = async { getActivationOrderToSign(signedChallenge) }
|
||||
val otpTaskDeferred = async { createWallet(session) }
|
||||
|
||||
val order = runCatching { activationOrderDeferred.await() }
|
||||
.getOrElse {
|
||||
otpTaskDeferred.cancel()
|
||||
return@coroutineScope CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||
}
|
||||
|
||||
otpTaskDeferred.await()
|
||||
|
||||
signOrder(session, order)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(TangemSdkError::class)
|
||||
private suspend fun getActivationOrderToSign(signedChallenge: VisaAuthSignedChallenge): ActivationOrder {
|
||||
val tokens = runCatching {
|
||||
visaAuthRepository.getAccessTokens(signedChallenge)
|
||||
}.getOrElse {
|
||||
throw TangemSdkError.Underlying(
|
||||
"Underlying network error: ${it.message ?: ""}",
|
||||
)
|
||||
}
|
||||
|
||||
visaAuthTokenStorage.store(tokens)
|
||||
|
||||
return visaActivationRepository.getActivationOrderToSign()
|
||||
}
|
||||
|
||||
private suspend fun createWallet(session: CardSession): CompletionResult<Unit> {
|
||||
coroutineScope { ensureActive() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
return if (card.wallets.any { it.curve == VisaUtilities.mandatoryCurve }) {
|
||||
createOTP(session)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
createWalletTask.run(session) { createWalletResult ->
|
||||
continuation.resume(createWalletResult)
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createOTP(session)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createOTP(session: CardSession): CompletionResult<Unit> {
|
||||
coroutineScope { ensureActive() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val otp = otpStorage.getOTP(card.cardId)
|
||||
return if (otp != null) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val otpCommand = GenerateOTPCommand()
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
otpCommand.run(session) { otpResult ->
|
||||
continuation.resume(otpResult)
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
otpStorage.saveOTP(card.cardId, result.data.rootOTP)
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun signOrder(
|
||||
session: CardSession,
|
||||
order: ActivationOrder,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet =
|
||||
card.wallets.firstOrNull() ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val task = SignHashCommand(order.hash.hexToBytes(), wallet.publicKey)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { signResult ->
|
||||
continuation.resume(signResult)
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
handleSignedOrder(
|
||||
session = session,
|
||||
activationOrder = order,
|
||||
response = result.data,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleSignedOrder(
|
||||
session: CardSession,
|
||||
activationOrder: ActivationOrder,
|
||||
response: SignHashResponse,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val signedOrder = SignedActivationOrder(
|
||||
activationOrder = activationOrder,
|
||||
signature = response.signature.toHexString(),
|
||||
)
|
||||
|
||||
val otp = otpStorage.getOTP(card.cardId) ?: return CompletionResult.Failure(
|
||||
TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message),
|
||||
)
|
||||
|
||||
val activationResponse = VisaCardActivationResponse(
|
||||
signedActivationOrder = signedOrder,
|
||||
rootOTP = VisaRootOTP(otp.toHexString()),
|
||||
)
|
||||
|
||||
return setupAccessCode(session).map { activationResponse }
|
||||
}
|
||||
|
||||
private suspend fun setupAccessCode(session: CardSession): CompletionResult<Unit> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
if (card.isAccessCodeSet) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
val task = SetUserCodeCommand.changeAccessCode(accessCode)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { setAccessCodeResult ->
|
||||
continuation.resume(setAccessCodeResult)
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
accessCode: String,
|
||||
challengeToSign: VisaAuthChallenge.Card?,
|
||||
activationInput: VisaActivationInput,
|
||||
coroutineScope: CoroutineScope,
|
||||
): VisaCardActivationTask
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ internal object UserWalletsListManagerModule {
|
|||
.add(TangemSdkAdapter.DateAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
|
||||
.add(VisaCardActivationStatus.serializer)
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.api.common.adapter.LocalDateAdapter
|
|||
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -34,6 +35,7 @@ class MoshiModule {
|
|||
.add(BigDecimalAdapter())
|
||||
.add(LocalDateAdapter())
|
||||
.add(DateTimeAdapter())
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.addStakeKitEnumFallbackAdapters()
|
||||
.build()
|
||||
|
|
@ -42,8 +44,26 @@ class MoshiModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
@SdkMoshi
|
||||
fun provideSdkMoshi(sdkMoshiJsonConverter: MoshiJsonConverter): Moshi {
|
||||
return sdkMoshiJsonConverter.moshi
|
||||
fun provideSdkMoshi(): Moshi {
|
||||
val adapters = MoshiJsonConverter.getTangemSdkAdapters() +
|
||||
listOf(
|
||||
BigDecimalAdapter(),
|
||||
WalletDerivedKeysMapAdapter(),
|
||||
ScanResponseDerivedKeysMapAdapter(),
|
||||
ByteArrayKeyAdapter(),
|
||||
ExtendedPublicKeysMapAdapter(),
|
||||
CardBackupStatusAdapter(),
|
||||
DerivationPathAdapterWithMigration(),
|
||||
)
|
||||
|
||||
val typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters()
|
||||
|
||||
return Moshi.Builder().apply {
|
||||
add(VisaCardActivationStatus.jsonAdapter)
|
||||
adapters.forEach { this.add(it) }
|
||||
typedAdapters.forEach { add(it.key, it.value) }
|
||||
add(KotlinJsonAdapterFactory())
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
interface VisaOTPStorage {
|
||||
|
||||
suspend fun saveOTP(cardId: String, otp: ByteArray)
|
||||
|
||||
suspend fun getOTP(cardId: String): ByteArray?
|
||||
|
||||
suspend fun removeOTP(cardId: String)
|
||||
}
|
||||
|
||||
suspend fun VisaOTPStorage.hasSavedOTP(cardId: String): Boolean = getOTP(cardId) != null
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.core.ui.components.fields.contextmenu
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.NonSkippableComposable
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbarStatus
|
||||
|
||||
/**
|
||||
* Composable component that used to disable context menu
|
||||
*
|
||||
* @param content content
|
||||
*/
|
||||
@Composable
|
||||
@NonSkippableComposable
|
||||
fun DisableContextMenu(content: @Composable () -> Unit) {
|
||||
CompositionLocalProvider(
|
||||
value = LocalTextToolbar provides EmptyTextToolbar,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
private object EmptyTextToolbar : TextToolbar {
|
||||
|
||||
override val status: TextToolbarStatus = TextToolbarStatus.Hidden
|
||||
|
||||
override fun hide() = Unit
|
||||
|
||||
override fun showMenu(
|
||||
rect: Rect,
|
||||
onCopyRequested: (() -> Unit)?,
|
||||
onPasteRequested: (() -> Unit)?,
|
||||
onCutRequested: (() -> Unit)?,
|
||||
onSelectAllRequested: (() -> Unit)?,
|
||||
) = Unit
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.visa
|
|||
|
||||
import com.tangem.data.visa.converter.VisaActivationStatusConverter
|
||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
||||
import com.tangem.domain.visa.model.ActivationOrder
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -20,4 +21,8 @@ internal class DefaultVisaActivationRepository @Inject constructor(
|
|||
visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus())
|
||||
// TODO implement refreshing access token if it's expired
|
||||
}
|
||||
|
||||
override suspend fun getActivationOrderToSign(): ActivationOrder {
|
||||
return ActivationOrder("TODO implement")
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.datasource.asset.loader.AssetLoader
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.demo.BuildConfig
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -370,7 +369,7 @@ class DefaultWalletManagersFacade(
|
|||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
if (BuildConfig.DEBUG || walletManager == null) {
|
||||
if (walletManager == null) {
|
||||
walletManager = walletManagerFactory.createWalletManager(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
blockchain = blockchain,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ internal object ScanResponseAsStringSerializer : KSerializer<ScanResponse> {
|
|||
.add(TangemSdkAdapter.DateAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
|
||||
.add(VisaCardActivationStatus.serializer)
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
data class ActivationOrder(
|
||||
val hash: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
data class SignedActivationOrder(
|
||||
val activationOrder: ActivationOrder,
|
||||
val signature: String,
|
||||
)
|
||||
|
|
@ -3,4 +3,6 @@ package com.tangem.domain.visa.model
|
|||
enum class VisaActivationError(val message: String) {
|
||||
BlockedForActivation("Card is blocked for activation"),
|
||||
InvalidActivationState("Invalid activation state"),
|
||||
WrongCard("Wrong card tapped"),
|
||||
MissingRootOTP("Missing root OTP"),
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
data class VisaCardActivationResponse(
|
||||
val signedActivationOrder: SignedActivationOrder,
|
||||
val rootOTP: VisaRootOTP,
|
||||
)
|
||||
|
|
@ -28,7 +28,7 @@ sealed class VisaCardActivationStatus {
|
|||
data object Blocked : VisaCardActivationStatus()
|
||||
|
||||
companion object {
|
||||
val serializer: PolymorphicJsonAdapterFactory<VisaCardActivationStatus>
|
||||
val jsonAdapter: PolymorphicJsonAdapterFactory<VisaCardActivationStatus>
|
||||
get() = PolymorphicJsonAdapterFactory.of(VisaCardActivationStatus::class.java, "type")
|
||||
.withSubtype(Activated::class.java, "Activated")
|
||||
.withSubtype(ActivationStarted::class.java, "ActivationStarted")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
@JvmInline
|
||||
value class VisaRootOTP(val value: String)
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.domain.visa.repository
|
||||
|
||||
import com.tangem.domain.visa.model.ActivationOrder
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
|
||||
interface VisaActivationRepository {
|
||||
|
||||
suspend fun getActivationRemoteState(): VisaActivationRemoteState
|
||||
|
||||
suspend fun getActivationOrderToSign(): ActivationOrder
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -24,6 +25,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
|
|
@ -88,6 +90,10 @@ internal fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import,
|
|||
label = stringResourceSafe(id = R.string.common_passphrase),
|
||||
placeholder = stringResourceSafe(id = R.string.send_optional_field),
|
||||
onIconClick = state.onPassphraseInfoClick,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +148,10 @@ private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifie
|
|||
singleLine = false,
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
visualTransformation = invalidWordsColorTransformation,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -2,19 +2,27 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.components.OutlineTextField
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.fields.contextmenu.DisableContextMenu
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -96,17 +104,30 @@ internal fun MultiWalletSeedPhraseWordsCheck(
|
|||
|
||||
@Composable
|
||||
private fun Fields(state: MultiWalletSeedPhraseUM.GeneratedWordsCheck, modifier: Modifier = Modifier) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
state.wordFields.forEach { field ->
|
||||
OutlineTextField(
|
||||
value = field.word,
|
||||
onValueChange = field.onChange,
|
||||
label = field.index.toString(),
|
||||
isError = field.error,
|
||||
)
|
||||
DisableContextMenu {
|
||||
state.wordFields.fastForEachIndexed { index, field ->
|
||||
OutlineTextField(
|
||||
value = field.word,
|
||||
onValueChange = field.onChange,
|
||||
label = field.index.toString(),
|
||||
isError = field.error,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = if (index == state.wordFields.lastIndex) ImeAction.Done else ImeAction.Next,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
onNext = { focusManager.moveFocus(focusDirection = FocusDirection.Down) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
|
|||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -13,6 +14,7 @@ import javax.inject.Inject
|
|||
@ComponentScoped
|
||||
internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
|
@ -44,7 +46,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
_uiState.update { it.copy(step = OnboardingVisaAccessCodeUM.Step.ReEnter) }
|
||||
}
|
||||
OnboardingVisaAccessCodeUM.Step.ReEnter -> {
|
||||
startActivationProcess()
|
||||
startActivationProcess(accessCode = uiState.value.accessCodeFirst.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,8 +69,26 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun startActivationProcess() {
|
||||
TODO()
|
||||
private fun startActivationProcess(accessCode: String) {
|
||||
modelScope.launch {
|
||||
@Suppress("UnusedPrivateMember")
|
||||
val result = tangemSdkManager.activateVisaCard(
|
||||
accessCode = accessCode,
|
||||
challengeToSign = null,
|
||||
activationInput = TODO(),
|
||||
)
|
||||
|
||||
// when (result) {
|
||||
// is CompletionResult.Success -> {
|
||||
// val response = result.data
|
||||
// TODO()
|
||||
// }
|
||||
// is CompletionResult.Failure -> {
|
||||
// // show alert
|
||||
// TODO()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,22 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.OutlineTextField
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.fields.contextmenu.DisableContextMenu
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock
|
||||
|
|
@ -72,25 +80,31 @@ fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modi
|
|||
@Composable
|
||||
private fun CheckSeedPhraseBlock(state: CheckSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
Column(modifier) {
|
||||
OutlineTextField(
|
||||
value = state.tvSecondPhrase.textFieldValue,
|
||||
onValueChange = state.tvSecondPhrase.onTextFieldValueChanged,
|
||||
label = state.tvSecondPhrase.label,
|
||||
isError = state.tvSecondPhrase.isError,
|
||||
)
|
||||
CheckSeedPhraseTextField(state = state.tvSecondPhrase, imeAction = ImeAction.Next)
|
||||
CheckSeedPhraseTextField(state = state.tvSeventhPhrase, imeAction = ImeAction.Next)
|
||||
CheckSeedPhraseTextField(state = state.tvEleventhPhrase, imeAction = ImeAction.Done)
|
||||
}
|
||||
}
|
||||
|
||||
OutlineTextField(
|
||||
value = state.tvSeventhPhrase.textFieldValue,
|
||||
onValueChange = state.tvSeventhPhrase.onTextFieldValueChanged,
|
||||
label = state.tvSeventhPhrase.label,
|
||||
isError = state.tvSeventhPhrase.isError,
|
||||
)
|
||||
@Composable
|
||||
private fun CheckSeedPhraseTextField(state: TextFieldState, imeAction: ImeAction) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
DisableContextMenu {
|
||||
OutlineTextField(
|
||||
value = state.tvEleventhPhrase.textFieldValue,
|
||||
onValueChange = state.tvEleventhPhrase.onTextFieldValueChanged,
|
||||
label = state.tvEleventhPhrase.label,
|
||||
isError = state.tvEleventhPhrase.isError,
|
||||
value = state.textFieldValue,
|
||||
onValueChange = state.onTextFieldValueChanged,
|
||||
label = state.label,
|
||||
isError = state.isError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = imeAction,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
onNext = { focusManager.moveFocus(focusDirection = FocusDirection.Down) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
|
|
@ -111,6 +113,10 @@ private fun PassphraseBlock(
|
|||
label = stringResourceSafe(id = R.string.common_passphrase),
|
||||
placeholder = stringResourceSafe(id = R.string.send_optional_field),
|
||||
onIconClick = onPassphraseInfoClick,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +141,10 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif
|
|||
style = SpanStyle(color = TangemTheme.colors.text.warning),
|
||||
),
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -70,13 +70,13 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
titleResId = R.string.feature_toggles,
|
||||
onBackClick = {},
|
||||
refreshButton = TopBarWithRefreshUM.RefreshButton(
|
||||
isVisible = false,
|
||||
isVisible = !isMatchLocalConfig,
|
||||
onRefreshClick = ::onRefreshClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
uiState.topBar.copy(
|
||||
refreshButton = uiState.topBar.refreshButton.copy(isVisible = isMatchLocalConfig),
|
||||
refreshButton = uiState.topBar.refreshButton.copy(isVisible = !isMatchLocalConfig),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.visa.models)
|
||||
|
||||
implementation(projects.core.res)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
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.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaCardActivationResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
|
|
@ -141,4 +144,14 @@ interface TangemSdkManager {
|
|||
fun clearProductType()
|
||||
|
||||
// endregion
|
||||
|
||||
// region Visa-specific
|
||||
|
||||
suspend fun activateVisaCard(
|
||||
accessCode: String,
|
||||
challengeToSign: VisaAuthChallenge.Card?,
|
||||
activationInput: VisaActivationInput,
|
||||
): CompletionResult<VisaCardActivationResponse>
|
||||
|
||||
// endregion
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue