Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-17 16:55:33 +05:00
parent 8b40cf4889
commit 9c6ce006aa
14 changed files with 109 additions and 20 deletions

View file

@ -103,10 +103,10 @@
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
<string name="app_settings_enable_biometrics_title">Enable biometric authentication</string>
<string name="app_settings_off_biometrics_alert_message">Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet.</string>
<string name="app_settings_off_require_access_code_alert_message">Youll be asked for your wallets access code later so we can securely store it for future use</string>
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
<string name="app_settings_off_require_access_code_alert_message">You\'ll be asked for your access code later for secure storage.</string>
<string name="app_settings_off_saved_access_code_alert_message">This will delete all saved wallet access codes. You\'ll need to enter the access code again to use the wallet.</string>
<string name="app_settings_off_saved_wallet_alert_message">Removing the saved devices deletes all the saved wallets and their access codes from the app.</string>
<string name="app_settings_on_require_access_code_alert_message">This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code.</string>
<string name="app_settings_on_require_access_code_alert_message">This will delete all saved wallet access codes. Youll need to enter the access code again to use the wallet.</string>
<string name="app_settings_require_access_code">Require Access Code</string>
<string name="app_settings_require_access_code_footer">This option turns off biometrics for sensitive actions. Youll need to enter your access code each time you sign a transaction.</string>
<string name="app_settings_saved_access_codes">Save Access Code</string>
@ -145,10 +145,10 @@
<string name="balance_hidden_title">Balances are hidden</string>
<string name="beta_mode_warning_message">According to the blockchain developers, Kaspa tokens are currently in beta. Stay tuned for updates!</string>
<string name="beta_mode_warning_title">Beta Mode</string>
<string name="biometric_disabled_warning_description">Biometrics are turned off on your device, so you cant use them to unlock your wallets. Enable biometrics in your device settings to use this method again.</string>
<string name="biometric_disabled_warning_description">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.</string>
<string name="biometric_disabled_warning_title">Biometric authentication disabled</string>
<string name="biometric_lockout_permanent_warning_description">Please scan the card or ring</string>
<string name="biometric_lockout_permanent_warning_description_2">Youve reached the limit of biometric attempts. Please unlock your wallet with a device tap or enter your access code.</string>
<string name="biometric_lockout_permanent_warning_description_2">You\'ve reached the limit of biometric attempts. Please unlock your wallet with a card/ring or enter your access code.</string>
<string name="biometric_lockout_permanent_warning_title">Biometric authentication locked</string>
<string name="biometric_lockout_warning_description">Please try again in 30 seconds or scan the card or ring</string>
<string name="biometric_lockout_warning_description_2">Biometric login is temporarily locked. Please try again in 30 seconds, or unlock your wallet with a device tap or access code.</string>
@ -182,7 +182,7 @@
<string name="card_reset_alert_finish_ok_button">Upgrade again</string>
<string name="card_reset_alert_finish_title">Reset complete</string>
<string name="card_reset_alert_incomplete_message">We recommend completing the reset process for all Tangem devices in this wallet.</string>
<string name="card_reset_alert_incomplete_title">You havent reset all your Tangem devices</string>
<string name="card_reset_alert_incomplete_title">Some Tangem devices still need to be reset.</string>
<string name="card_settings_access_code_recovery_disabled_description">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.</string>
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
@ -384,8 +384,9 @@
<string name="common_transaction_status">Transaction status</string>
<string name="common_transactions">Transactions</string>
<string name="common_transfer">Transfer</string>
<string name="common_unable_to_load">Unable to load the data…</string>
<string name="common_unable_to_load">Unable to load data…</string>
<string name="common_understand">I understand</string>
<string name="common_understand_continue">I understand, continue</string>
<string name="common_unknown_error">There was an error. Please try again.</string>
<string name="common_unreachable">Unreachable</string>
<string name="common_unstake">Unstake</string>
@ -575,7 +576,7 @@
<string name="home_button_create_new_wallet">Create New Wallet</string>
<string name="home_button_order">Order Tangem</string>
<string name="home_button_scan">Scan Tangem</string>
<string name="hot_access_code_set_biometric_ask">Do you want to allow “Tangem” to use biometric authentication? To confirm your identity and open the app</string>
<string name="hot_access_code_set_biometric_ask">Do you want to allow \"Tangem\" to use biometric authentication to confirm your identity and open the app?</string>
<string name="hot_crypto_add_token_subtitle">to %s</string>
<string name="hot_crypto_token_network">On %s network</string>
<string name="hw_access_code_create_alert_title">Are you sure you want to cancel access code setup?</string>
@ -799,7 +800,7 @@
<string name="markets_tooltip_title">Add tokens</string>
<string name="markets_yield_supply_banner_description">Power up your assets while supplying them with instant access. %s</string>
<string name="markets_yield_supply_banner_title">Activate Yield Mode</string>
<string name="mobile_wallet_requires_min_os_warning_body">You must update to %1$s in order to create mobile wallet</string>
<string name="mobile_wallet_requires_min_os_warning_body">You must update to %1$s before creating a mobile wallet</string>
<string name="mobile_wallet_requires_min_os_warning_title">Mobile Wallet requires %1$s or later</string>
<string name="news_all_news">All news</string>
<plurals name="news_published_hours_ago">
@ -1426,6 +1427,7 @@
<string name="tangem_pay_freeze_card_success">Your card is frozen.</string>
<string name="tangem_pay_get_help">Get Help</string>
<string name="tangem_pay_other">Other</string>
<string name="tangem_pay_rooted_device_subtitle">Unable to use on rooted devices</string>
<string name="tangem_pay_status_completed">Completed</string>
<string name="tangem_pay_status_declined">Declined</string>
<string name="tangem_pay_status_pending">Pending</string>
@ -1598,7 +1600,7 @@
<string name="user_push_notification_agreement_header">Would you like to use\nPush-notifications?</string>
<string name="user_push_notification_banner_subtitle">Enable push notifications to receive alerts when funds arrive in your wallet.</string>
<string name="user_push_notification_banner_title">Don\'t Miss a Transaction</string>
<string name="user_wallet_list_add_button">Add new wallet</string>
<string name="user_wallet_list_add_button">Add Wallet</string>
<string name="user_wallet_list_delete_hw_prompt">If you delete this wallet without a backup, you will permanently lose access to your funds.</string>
<string name="user_wallet_list_delete_prompt">Are you sure you want to forget this wallet?</string>
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card or ring to log in</string>
@ -1759,7 +1761,7 @@
<string name="wallet_settings_change_access_code_title">Change access code</string>
<string name="wallet_settings_push_notifications_description">Stay notified on wallet incoming transactions and Tangem updates.</string>
<string name="wallet_settings_push_notifications_huawei_warning">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!</string>
<string name="wallet_settings_push_notifications_title">Transaction Notifications</string>
<string name="wallet_settings_push_notifications_title">Transaction notifications</string>
<string name="wallet_settings_set_access_code_title">Set access code</string>
<string name="wallet_settings_title">Wallet settings</string>
<string name="wallet_title">Tangem</string>
@ -1952,7 +1954,7 @@
<string name="wc_warning_transaction">Suspicious transaction</string>
<string name="welcome_create_wallet_already_have">Already have Tangem Wallet?</string>
<string name="welcome_create_wallet_feature_assets">Thousands of assets</string>
<string name="welcome_create_wallet_feature_class">Best in class hardware wallet</string>
<string name="welcome_create_wallet_feature_class">Top-tier hardware wallet</string>
<string name="welcome_create_wallet_feature_delivery">Fast delivery</string>
<string name="welcome_create_wallet_feature_one_tap">Start in one tap</string>
<string name="welcome_create_wallet_feature_seamless">Seamless and secure</string>
@ -1961,7 +1963,7 @@
<string name="welcome_create_wallet_mobile_description">Create or import a software wallet</string>
<string name="welcome_create_wallet_mobile_description_full">Create or import a software wallet on your phone.</string>
<string name="welcome_create_wallet_mobile_title">Start with Mobile Wallet</string>
<string name="welcome_create_wallet_other_method">Other method</string>
<string name="welcome_create_wallet_other_method">Other methods</string>
<string name="welcome_create_wallet_use_hardware_description">Use a Tangem hardware wallet</string>
<string name="welcome_create_wallet_use_hardware_title">Learn more &amp; buy</string>
<string name="welcome_interrupted_backup_alert_discard">Discard</string>

View file

@ -4,4 +4,6 @@ interface DeviceSecurityInfoProvider {
val isRooted: Boolean
val isBootloaderUnlocked: Boolean
val isXposed: Boolean
}
}
fun DeviceSecurityInfoProvider.isSecurityExposed(): Boolean = isRooted || isBootloaderUnlocked || isXposed

View file

@ -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 */

View file

@ -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,
)
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -5,4 +5,5 @@ sealed interface TangemPayCustomerInfoError {
data object UnavailableError : TangemPayCustomerInfoError
data object RefreshNeededError : TangemPayCustomerInfoError
data object UnknownError : TangemPayCustomerInfoError
data object ExposedDeviceError : TangemPayCustomerInfoError
}

View file

@ -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<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
@ -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 ->

View file

@ -104,7 +104,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
else -> back()
}
}
.onLeft { back() }
.onLeft { startOnboarding(userWalletId) }
}
}

View file

@ -44,4 +44,6 @@ internal sealed class TangemPayState {
) : TangemPayState()
data class TemporaryUnavailable(val notification: WalletNotification) : TangemPayState()
data object ExposedDevice : TangemPayState()
}

View file

@ -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
}
}
}

View file

@ -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(

View file

@ -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,
)
}
}

View file

@ -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),