From 46a28ab22c7a2bdc05f06918b19124b472974c3b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Feb 2025 14:22:10 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/di/domain/VisaDomainModule.kt | 17 +++-- .../datasource/api/visa/TangemVisaApi.kt | 7 ++ .../visa/models/request/SetPinCodeRequest.kt | 18 +++++ .../visa/DefaultVisaActivationRepository.kt | 25 +++++++ .../data/visa/MockVisaActivationRepository.kt | 2 + .../domain/visa/model/VisaEncryptedPinCode.kt | 8 ++ .../domain/visa/SetVisaPinCodeUseCase.kt | 75 +++++++++++++++++++ .../repository/VisaActivationRepository.kt | 2 + .../impl/DefaultOnboardingVisaComponent.kt | 19 ++++- .../pincode/OnboardingVisaPinCodeComponent.kt | 1 + .../model/OnboardingVisaPinCodeModel.kt | 21 +++++- .../v2/visa/impl/model/OnboardingVisaModel.kt | 2 +- .../v2/visa/impl/route/OnboardingVisaRoute.kt | 2 +- .../onboarding/v2/visa/impl/route/Step.kt | 2 +- .../onboarding/v2/visa/impl/route/Titles.kt | 2 +- 15 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt create mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaEncryptedPinCode.kt create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt index 479be1cc3c..de64fc496f 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/VisaDomainModule.kt @@ -3,32 +3,37 @@ package com.tangem.tap.di.domain import com.tangem.domain.visa.GetVisaCurrencyUseCase import com.tangem.domain.visa.GetVisaTxDetailsUseCase import com.tangem.domain.visa.GetVisaTxHistoryUseCase +import com.tangem.domain.visa.SetVisaPinCodeUseCase +import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped +import dagger.hilt.components.SingletonComponent @Module -@InstallIn(ViewModelComponent::class) +@InstallIn(SingletonComponent::class) internal object VisaDomainModule { @Provides - @ViewModelScoped fun provideVisaCurrencyUseCase(visaRepository: VisaRepository): GetVisaCurrencyUseCase { return GetVisaCurrencyUseCase(visaRepository) } @Provides - @ViewModelScoped fun provideGetVisaTxHistoryUseCase(visaRepository: VisaRepository): GetVisaTxHistoryUseCase { return GetVisaTxHistoryUseCase(visaRepository) } @Provides - @ViewModelScoped fun provideGetVisaTxDetailsUseCase(visaRepository: VisaRepository): GetVisaTxDetailsUseCase { return GetVisaTxDetailsUseCase(visaRepository) } + + @Provides + fun provideSetVisaPinCodeUseCase( + visaActivationRepositoryFactory: VisaActivationRepository.Factory, + ): SetVisaPinCodeUseCase { + return SetVisaPinCodeUseCase(visaActivationRepositoryFactory) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt index 8050897cbf..802738fbd7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.utils.ReadTimeout import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest +import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse import com.tangem.datasource.api.visa.models.response.CardWalletDataToSignResponse import com.tangem.datasource.api.visa.models.response.CustomerWalletDataToSignResponse @@ -64,4 +65,10 @@ interface TangemVisaApi { @Header("Authorization") authHeader: String, @Body body: ActivationByCustomerWalletRequest, ): ApiResponse + + @POST("product_instance/issuer_activation") + suspend fun setPinCode( + @Header("Authorization") authHeader: String, + @Body body: SetPinCodeRequest, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt new file mode 100644 index 0000000000..c3a5290c8c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt @@ -0,0 +1,18 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SetPinCodeRequest( + @Json(name = "customer_id") val customerId: String, + @Json(name = "activation_order_id") val activationOrderId: String, + @Json(name = "product_instance_id") val productInstanceId: String, + @Json(name = "data") val data: Data, +) { + data class Data( + @Json(name = "session_key") val sessionKey: String, + @Json(name = "iv") val iv: String, + @Json(name = "encrypted_pin") val encryptedPin: String, + ) +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index f96179c10c..4bb73eee50 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -7,6 +7,7 @@ import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.visa.TangemVisaApi import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest +import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.domain.visa.exception.RefreshTokenExpiredException import com.tangem.domain.visa.model.* @@ -167,6 +168,30 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( } } + override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) { + withContext(dispatcherProvider.io) { + request { + val authTokens = + checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } + val accessCodeData = accessCodeDataConverter.convert(authTokens) + + visaApi.setPinCode( + authHeader = authTokens.getAuthHeader(), + body = SetPinCodeRequest( + customerId = accessCodeData.customerId, + productInstanceId = accessCodeData.productInstanceId, + activationOrderId = pinCode.activationOrderId, + data = SetPinCodeRequest.Data( + sessionKey = pinCode.sessionId, + iv = pinCode.iv, + encryptedPin = pinCode.encryptedPin, + ), + ), + ) + } + } + } + private suspend fun request(requestBlock: suspend () -> T): T { return runCatching { requestBlock() diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt index 1da6cd3be2..4862fb729c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt @@ -43,6 +43,8 @@ class MockVisaActivationRepository @AssistedInject constructor( override suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet) {} + override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) {} + @AssistedFactory interface Factory : VisaActivationRepository.Factory { override fun create(cardId: VisaCardId): MockVisaActivationRepository diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaEncryptedPinCode.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaEncryptedPinCode.kt new file mode 100644 index 0000000000..865e58ea68 --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaEncryptedPinCode.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.visa.model + +data class VisaEncryptedPinCode( + val activationOrderId: String, + val sessionId: String, + val iv: String, + val encryptedPin: String, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt new file mode 100644 index 0000000000..7b05cf26b8 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt @@ -0,0 +1,75 @@ +@file:Suppress("MaximumLineLength") +package com.tangem.domain.visa + +import android.util.Base64 +import arrow.core.Either +import com.tangem.domain.visa.model.VisaCardId +import com.tangem.domain.visa.model.VisaEncryptedPinCode +import com.tangem.domain.visa.repository.VisaActivationRepository +import java.security.Key +import java.security.KeyFactory +import java.security.PublicKey +import java.security.SecureRandom +import java.security.spec.X509EncodedKeySpec +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +private const val KEY_SIZE = 256 + +private const val RSA_PUB_KEY = "TODO" // TODO add RSA public key [REDACTED_TASK_KEY] + +class SetVisaPinCodeUseCase( + private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, +) { + + suspend operator fun invoke( + visaCardId: VisaCardId, + activationOrderId: String, + pinCode: String, + ): Either = Either.catch { + val visaActivationRepository = visaActivationRepositoryFactory.create(visaCardId) + + val sessionKey = generateSessionKey() + val sessionId = getSessionId(RSA_PUB_KEY, sessionKey) + + val secureRandom = SecureRandom() + val iv = ByteArray(size = 16) + secureRandom.nextBytes(iv) + val ivParameterSpec = IvParameterSpec(iv) + val ivPayload = Base64.encodeToString(iv, Base64.NO_WRAP) + val aesKey = SecretKeySpec(sessionKey.encoded, 0, sessionKey.encoded.size, "AES") + val aesCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") + aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec) + val encryptedPin = Base64.encodeToString(aesCipher.doFinal(pinCode.toByteArray()), Base64.NO_WRAP) + + visaActivationRepository.sendPinCode( + VisaEncryptedPinCode( + activationOrderId = activationOrderId, + sessionId = sessionId, + iv = ivPayload, + encryptedPin = encryptedPin, + ), + ) + } + + private fun generateSessionKey(): Key { + val generator = KeyGenerator.getInstance("AES") + generator.init(KEY_SIZE) + return generator.generateKey() + } + + private fun getPublicKey(rsaPublicKey: String): PublicKey { + val keyBytes = Base64.decode(rsaPublicKey, Base64.NO_WRAP) + val spec = X509EncodedKeySpec(keyBytes) + return KeyFactory.getInstance("RSA").generatePublic(spec) + } + + private fun getSessionId(rsaPublicKey: String, sessionKey: Key): String { + val publicKey = getPublicKey(rsaPublicKey) + val cipher = Cipher.getInstance("RSA") + cipher.init(Cipher.ENCRYPT_MODE, publicKey) + return Base64.encodeToString(cipher.doFinal(sessionKey.encoded), Base64.NO_WRAP) + } +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt index 35043bcac1..5f7959f423 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt @@ -18,6 +18,8 @@ interface VisaActivationRepository { suspend fun approveByCustomerWallet(signedData: VisaSignedDataByCustomerWallet) + suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) + interface Factory { fun create(cardId: VisaCardId): VisaActivationRepository } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt index 3fe2267ca4..1e987bf695 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt @@ -126,7 +126,13 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ), params = OnboardingVisaInProgressComponent.Params( childParams = childParams, - onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) }, + onDone = { + model.stackNavigation.push( + OnboardingVisaRoute.PinCode( + activationOrderId = "TODO", // TODO [REDACTED_TASK_KEY] [Visa] In progress screen after pin code + ), + ) + }, ), ) is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( @@ -137,13 +143,20 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ), params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, - onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) }, + onDone = { + model.stackNavigation.push( + OnboardingVisaRoute.PinCode( + activationOrderId = route.visaDataForApprove.dataToSign.request.orderId, + ), + ) + }, ), ) - OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent( + is OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent( appComponentContext = factoryContext, config = OnboardingVisaPinCodeComponent.Config( scanResponse = model.currentScanResponse.value, + activationOrderId = route.activationOrderId, ), params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt index edf60844f7..e6840ae79b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt @@ -46,6 +46,7 @@ internal class OnboardingVisaPinCodeComponent( data class Config( val scanResponse: ScanResponse, + val activationOrderId: String, ) data class Params( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt index 38f42fad17..771cb7dfe7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt @@ -1,12 +1,15 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model import androidx.compose.runtime.Stable +import com.tangem.common.extensions.toHexString import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.SetVisaPinCodeUseCase import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet @@ -24,6 +27,7 @@ import javax.inject.Inject @Stable @ComponentScoped +@Suppress("LongParameterList") internal class OnboardingVisaPinCodeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @@ -31,9 +35,14 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val authTokenStorage: VisaAuthTokenStorage, private val otpStorage: VisaAuthTokenStorage, + private val setVisaPinCodeUseCase: SetVisaPinCodeUseCase, ) : Model() { private val params = paramsContainer.require() + private val visaCardId = VisaCardId( + cardId = params.scanResponse.card.cardId, + cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(), + ) private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() @@ -41,6 +50,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( private fun getInitialState(): OnboardingVisaPinCodeUM { return OnboardingVisaPinCodeUM( + submitButtonEnabled = false, onPinCodeChange = ::onPinCodeChange, onSubmitClick = ::onSubmitClick, ) @@ -63,8 +73,15 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( modelScope.launch { loading(true) - // TODO - // make backend call + setVisaPinCodeUseCase( + pinCode = _uiState.value.pinCode, + visaCardId = visaCardId, + activationOrderId = params.activationOrderId, + ).onLeft { + loading(false) + return@launch + } + saveWallet() loading(false) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt index f746570a63..e65980a929 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt @@ -119,7 +119,7 @@ internal class OnboardingVisaModel @Inject constructor( is OnboardingVisaRoute.Welcome, is OnboardingVisaRoute.ChooseWallet, OnboardingVisaRoute.InProgress, - OnboardingVisaRoute.PinCode, + is OnboardingVisaRoute.PinCode, -> { } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt index 64f7576d0f..5abb23b300 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt @@ -28,5 +28,5 @@ sealed class OnboardingVisaRoute { data object InProgress : OnboardingVisaRoute() @Serializable - data object PinCode : OnboardingVisaRoute() + data class PinCode(val activationOrderId: String) : OnboardingVisaRoute() } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt index a4deb7fcdb..4edd313357 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt @@ -8,7 +8,7 @@ internal fun OnboardingVisaRoute.stepNum() = when (this) { is OnboardingVisaRoute.OtherWalletApproveOption -> 4 is OnboardingVisaRoute.TangemWalletApproveOption -> 4 OnboardingVisaRoute.InProgress -> 5 - OnboardingVisaRoute.PinCode -> 6 + is OnboardingVisaRoute.PinCode -> 6 } internal const val ONBOARDING_VISA_STEPS_COUNT = 7 \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt index 89e6c7a044..adb5cebf9b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt @@ -8,7 +8,7 @@ internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) { is OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation") OnboardingVisaRoute.InProgress -> TextReference.Str("In progress") is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") - OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") + is OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection") is OnboardingVisaRoute.Welcome -> TextReference.Str("Getting started") } \ No newline at end of file