From 9c6ce006aa283be31add975803eada3645c36b25 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Dec 2025 16:55:33 +0500 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 28 +++++++------ .../security/DeviceSecurityInfoProvider.kt | 4 +- data/visa/build.gradle.kts | 1 + .../tangem/data/pay/di/TangemPayDataModule.kt | 3 ++ .../repository/DefaultOnboardingRepository.kt | 2 +- domain/visa/build.gradle.kts | 5 ++- .../pay/model/TangemPayCustomerInfoError.kt | 1 + .../TangemPayMainScreenCustomerInfoUseCase.kt | 13 ++++++ .../model/TangemPayOnboardingModel.kt | 2 +- .../wallet/state/model/TangemPayState.kt | 2 + .../TangemPayExposedDeviceTransformer.kt | 17 ++++++++ .../subscribers/TangemPayMainSubscriber.kt | 5 ++- .../visa/TangemPayExposedDeviceState.kt | 41 +++++++++++++++++++ .../visa/TangemPayMainScreenBlock.kt | 5 ++- 14 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayExposedDeviceTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayExposedDeviceState.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index d365c6e247..524e2ec97f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -103,10 +103,10 @@ Go to settings to enable biometric authentication in the Tangem App Enable biometric authentication Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet. - You’ll be asked for your wallet’s access code later so we can securely store it for future use - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + You\'ll be asked for your access code later for secure storage. + This will delete all saved wallet access codes. You\'ll need to enter the access code again to use the wallet. Removing the saved devices deletes all the saved wallets and their access codes from the app. - This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code. + This will delete all saved wallet access codes. You’ll need to enter the access code again to use the wallet. Require Access Code This option turns off biometrics for sensitive actions. You’ll need to enter your access code each time you sign a transaction. Save Access Code @@ -145,10 +145,10 @@ Balances are hidden According to the blockchain developers, Kaspa tokens are currently in beta. Stay tuned for updates! Beta Mode - Biometrics are turned off on your device, so you can’t use them to unlock your wallets. Enable biometrics in your device settings to use this method again. + Biometrics are turned off on your device, so you can\'t use them to unlock your wallets. Enable biometrics in your device settings to use this method again. Biometric authentication disabled Please scan the card or ring - You’ve reached the limit of biometric attempts. Please unlock your wallet with a device tap or enter your access code. + You\'ve reached the limit of biometric attempts. Please unlock your wallet with a card/ring or enter your access code. Biometric authentication locked Please try again in 30 seconds or scan the card or ring Biometric login is temporarily locked. Please try again in 30 seconds, or unlock your wallet with a device tap or access code. @@ -182,7 +182,7 @@ Upgrade again Reset complete We recommend completing the reset process for all Tangem devices in this wallet. - You haven’t reset all your Tangem devices + Some Tangem devices still need to be reset. Disable this option if you don\'t want this card to be used to reset access codes on other cards or rings in this wallet. Please note that this will also prevent you from resetting the access code on this card. Allows you to use this card to reset access code on other cards in this wallet Access code recovery @@ -384,8 +384,9 @@ Transaction status Transactions Transfer - Unable to load the data… + Unable to load data… I understand + I understand, continue There was an error. Please try again. Unreachable Unstake @@ -575,7 +576,7 @@ Create New Wallet Order Tangem Scan Tangem - Do you want to allow “Tangem” to use biometric authentication? To confirm your identity and open the app + Do you want to allow \"Tangem\" to use biometric authentication to confirm your identity and open the app? to %s On %s network Are you sure you want to cancel access code setup? @@ -799,7 +800,7 @@ Add tokens Power up your assets while supplying them with instant access. %s Activate Yield Mode - You must update to %1$s in order to create mobile wallet + You must update to %1$s before creating a mobile wallet Mobile Wallet requires %1$s or later All news @@ -1426,6 +1427,7 @@ Your card is frozen. Get Help Other + Unable to use on rooted devices Completed Declined Pending @@ -1598,7 +1600,7 @@ Would you like to use\nPush-notifications? Enable push notifications to receive alerts when funds arrive in your wallet. Don\'t Miss a Transaction - Add new wallet + Add Wallet If you delete this wallet without a backup, you will permanently lose access to your funds. Are you sure you want to forget this wallet? An error has occurred, please scan your card or ring to log in @@ -1759,7 +1761,7 @@ Change access code Stay notified on wallet incoming transactions and Tangem updates. Push notifications may currently not work on Huawei devices. We\'re actively working on a solution and will release a fix in an upcoming update. Thank you for your understanding! - Transaction Notifications + Transaction notifications Set access code Wallet settings Tangem @@ -1952,7 +1954,7 @@ Suspicious transaction Already have Tangem Wallet? Thousands of assets - Best in class hardware wallet + Top-tier hardware wallet Fast delivery Start in one tap Seamless and secure @@ -1961,7 +1963,7 @@ Create or import a software wallet Create or import a software wallet on your phone. Start with Mobile Wallet - Other method + Other methods Use a Tangem hardware wallet Learn more & buy Discard diff --git a/core/security/src/main/kotlin/com/tangem/security/DeviceSecurityInfoProvider.kt b/core/security/src/main/kotlin/com/tangem/security/DeviceSecurityInfoProvider.kt index 5be549d653..09d16c0a94 100644 --- a/core/security/src/main/kotlin/com/tangem/security/DeviceSecurityInfoProvider.kt +++ b/core/security/src/main/kotlin/com/tangem/security/DeviceSecurityInfoProvider.kt @@ -4,4 +4,6 @@ interface DeviceSecurityInfoProvider { val isRooted: Boolean val isBootloaderUnlocked: Boolean val isXposed: Boolean -} \ No newline at end of file +} + +fun DeviceSecurityInfoProvider.isSecurityExposed(): Boolean = isRooted || isBootloaderUnlocked || isXposed \ No newline at end of file diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 287bd65478..1470c2b5d9 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { /** Project - Data */ implementation(projects.core.datasource) implementation(projects.core.error) + implementation(projects.core.security) implementation(projects.data.common) /** Project - Domain */ diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index ff79c711bf..ffcd79fb85 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -13,6 +13,7 @@ import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase import com.tangem.domain.tangempay.TangemPayWithdrawUseCase import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository +import com.tangem.security.DeviceSecurityInfoProvider import dagger.Binds import dagger.Module import dagger.Provides @@ -76,12 +77,14 @@ internal interface TangemPayDataModule { customerOrderRepository: CustomerOrderRepository, tangemPayOnboardingRepository: OnboardingRepository, eligibilityManager: TangemPayEligibilityManager, + deviceSecurity: DeviceSecurityInfoProvider, ): TangemPayMainScreenCustomerInfoUseCase { return TangemPayMainScreenCustomerInfoUseCase( repository = repository, customerOrderRepository = customerOrderRepository, tangemPayOnboardingRepository = tangemPayOnboardingRepository, eligibilityManager = eligibilityManager, + deviceSecurity = deviceSecurity, ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index c8eb5b8e48..d2059f3785 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -49,7 +49,7 @@ internal class DefaultOnboardingRepository @Inject constructor( return requestHelper.performWithStaticToken { tangemPayApi.validateDeeplink(body = DeeplinkValidityRequest(link = link)) }.map { response -> - response.result?.status == VALID_STATUS + response.result?.status.equals(VALID_STATUS, ignoreCase = true) } } diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 1f83cc44b1..3c13ebe644 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -12,9 +12,10 @@ android { dependencies { /** Project - Core */ api(projects.core.pagination) - implementation(projects.core.utils) - implementation(projects.core.error) implementation(projects.core.analytics.models) + implementation(projects.core.error) + implementation(projects.core.security) + implementation(projects.core.utils) /** Project - Domain */ api(projects.domain.models) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCustomerInfoError.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCustomerInfoError.kt index 7fda6cfe51..e2086cfed3 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCustomerInfoError.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCustomerInfoError.kt @@ -5,4 +5,5 @@ sealed interface TangemPayCustomerInfoError { data object UnavailableError : TangemPayCustomerInfoError data object RefreshNeededError : TangemPayCustomerInfoError data object UnknownError : TangemPayCustomerInfoError + data object ExposedDeviceError : TangemPayCustomerInfoError } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index b84b7f061b..10a21fbb78 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -9,6 +9,8 @@ import com.tangem.domain.pay.model.* import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.visa.error.VisaApiError +import com.tangem.security.DeviceSecurityInfoProvider +import com.tangem.security.isSecurityExposed import kotlinx.coroutines.flow.* import timber.log.Timber @@ -23,6 +25,7 @@ class TangemPayMainScreenCustomerInfoUseCase( private val customerOrderRepository: CustomerOrderRepository, private val tangemPayOnboardingRepository: OnboardingRepository, private val eligibilityManager: TangemPayEligibilityManager, + private val deviceSecurity: DeviceSecurityInfoProvider, ) { val state: StateFlow>> @@ -30,6 +33,16 @@ class TangemPayMainScreenCustomerInfoUseCase( suspend fun fetch(userWalletId: UserWalletId) { Timber.tag(TAG).i("fetch: $userWalletId") + + if (deviceSecurity.isSecurityExposed()) { + Timber.tag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}") + Timber.tag(TAG).i("fetch security info: xposed: ${deviceSecurity.isXposed}") + Timber.tag(TAG).i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}") + + updateState(userWalletId = userWalletId, either = TangemPayCustomerInfoError.ExposedDeviceError.left()) + return // fast exit + } + repository.checkCustomerWallet(userWalletId) .fold( ifLeft = { error -> diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt index ff881de155..bc39e6d47b 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt @@ -104,7 +104,7 @@ internal class TangemPayOnboardingModel @Inject constructor( else -> back() } } - .onLeft { back() } + .onLeft { startOnboarding(userWalletId) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt index 25c8c2f0a5..4eae554a24 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt @@ -44,4 +44,6 @@ internal sealed class TangemPayState { ) : TangemPayState() data class TemporaryUnavailable(val notification: WalletNotification) : TangemPayState() + + data object ExposedDevice : TangemPayState() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayExposedDeviceTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayExposedDeviceTransformer.kt new file mode 100644 index 0000000000..b8a735a4f4 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayExposedDeviceTransformer.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState + +internal class TangemPayExposedDeviceTransformer( + userWalletId: UserWalletId, +) : WalletStateTransformer(userWalletId) { + override fun transform(prevState: WalletState): WalletState { + return if (prevState is WalletState.MultiCurrency.Content) { + prevState.copy(tangemPayState = TangemPayState.ExposedDevice) + } else { + prevState + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt index 2f3bad4547..6a7cf0a950 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt @@ -58,7 +58,10 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( transformer = TangemPayUnavailableStateTransformer(userWalletId), ) } - else -> { + TangemPayCustomerInfoError.ExposedDeviceError -> { + stateController.update(TangemPayExposedDeviceTransformer(userWalletId)) + } + TangemPayCustomerInfoError.UnknownError -> { // hide TangemPay block Timber.e("Failed when loading main screen TangemPay info: $tangemPayError") stateController.update( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayExposedDeviceState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayExposedDeviceState.kt new file mode 100644 index 0000000000..6ba66da79f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayExposedDeviceState.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.visa + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import com.tangem.core.ui.R +import com.tangem.core.ui.components.block.BlockCard +import com.tangem.core.ui.components.inputrow.InputRowImageBase +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme + +private const val DISABLED_ALPHA = 0.6F + +@Composable +internal fun TangemPayExposedDeviceState(modifier: Modifier = Modifier) { + BlockCard( + modifier = modifier + .clip(RoundedCornerShape(size = TangemTheme.dimens.radius14)) + .background(TangemTheme.colors.background.primary) + .alpha(DISABLED_ALPHA), + enabled = false, + onClick = {}, + ) { + InputRowImageBase( + modifier = Modifier + .padding( + all = TangemTheme.dimens.spacing12, + ), + subtitle = resourceReference(R.string.tangempay_payment_account), + caption = resourceReference(R.string.tangem_pay_rooted_device_subtitle), + subtitleColor = TangemTheme.colors.text.primary1, + captionColor = TangemTheme.colors.text.tertiary, + iconResWebp = R.drawable.img_visa_36, + endIconTint = TangemTheme.colors.icon.warning, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt index 7ee74d5ec2..b8f7e9a587 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt @@ -24,7 +24,8 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier) is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier) is TangemPayState.OnboardingBanner -> TangemPayOnboardingBanner(state, modifier) - TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier) + is TangemPayState.ExposedDevice -> TangemPayExposedDeviceState(modifier) + is TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier) } } @@ -36,6 +37,8 @@ private fun TangemPayMainScreenBlockPreview() { Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false) + TangemPayMainScreenBlock(state = TangemPayState.ExposedDevice, isBalanceHidden = false) + TangemPayMainScreenBlock( Progress( title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),