Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-20 12:21:24 +03:00
commit 2da48c037a
961 changed files with 24212 additions and 13399 deletions

View file

@ -4,12 +4,12 @@ import android.content.res.Resources
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.*
import com.tangem.common.biometric.BiometricManager
import com.tangem.common.authentication.KeystoreManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.crypto.bip39.DefaultMnemonic
@ -25,12 +25,12 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
@ -38,31 +38,38 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
@Suppress("TooManyFunctions")
class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources) {
class TangemSdkManager(
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val resources: Resources,
) {
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
private val userCodeRepository by lazy {
UserCodeRepository(
biometricManager = tangemSdk.biometricManager,
keystoreManager = tangemSdk.keystoreManager,
secureStorage = tangemSdk.secureStorage,
)
}
val canUseBiometry: Boolean
get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
val needEnrollBiometrics: Boolean
get() = tangemSdk.biometricManager.canEnrollBiometrics
get() = tangemSdk.authenticationManager.needEnrollBiometrics
val biometricManager: BiometricManager
get() = tangemSdk.biometricManager
val keystoreManager: KeystoreManager
get() = tangemSdk.keystoreManager
val secureStorage: SecureStorage
get() = tangemSdk.secureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
get() = tangemSdk.config.userCodeRequestPolicy
suspend fun scanProduct(
userTokensRepository: UserTokensRepository,
cardId: String? = null,
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse> {
@ -70,8 +77,7 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
return runTaskAsyncReturnOnMain(
runnable = ScanProductTask(
card = null,
userTokensRepository = userTokensRepository,
additionalBlockchainsToDerive = additionalBlockchainsToDerive,
derivationsFinder = derivationsFinder,
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
),
cardId = cardId,
@ -139,7 +145,9 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
return runTaskAsyncReturnOnMain(
runnable = ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository),
runnable = ResetToFactorySettingsTask(
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
),
cardId = cardId,
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
)
@ -244,17 +252,8 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
return resources.getString(stringResId, *formatArgs)
}
fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) {
tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
} else {
UserCodeRequestPolicy.Default
}
}
fun useBiometricsForAccessCode(): Boolean {
val policy = tangemSdk.config.userCodeRequestPolicy
return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
tangemSdk.config.userCodeRequestPolicy = policy
}
companion object {

View file

@ -19,6 +19,7 @@ import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -28,11 +29,13 @@ import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.rekotlin.Store
import timber.log.Timber
class TapWalletManager(
@ -75,17 +78,34 @@ class TapWalletManager(
withMainContext {
// Order is important
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
store.dispatch(WalletAction.UserWalletChanged(userWallet))
store.dispatch(WalletAction.UpdateCanSaveUserWallets(preferencesStorage.shouldSaveUserWallets))
store.dispatchWalletAction(action = WalletAction.UserWalletChanged(userWallet))
store.dispatchWalletAction(
action = WalletAction.UpdateCanSaveUserWallets(
canSaveUserWallets = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.shouldSaveUserWalletsSync(),
),
)
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
store.dispatch(WalletConnectAction.ResetState)
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
store.dispatchWalletAction(action = WalletAction.Warnings.CheckIfNeeded)
}
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
setupWalletConnectV2(userWallet)
loadData(userWallet = userWallet, refresh = refresh)
}
}
private fun Store<AppState>.dispatchWalletAction(action: WalletAction) {
val walletFeatureToggles = state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
dispatch(action = action)
}
setupWalletConnectV2(userWallet)
loadData(userWallet, refresh)
}
private fun setupWalletConnectV2(userWallet: UserWallet) {
@ -142,21 +162,31 @@ class TapWalletManager(
}
}
private fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
return store.state.walletState.walletManagers
.mapNotNull {
val wallet = it.wallet
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
val walletManagerToggles = store.state.daggerGraphState
.get(DaggerGraphState::walletFeatureToggles)
val walletManagers = if (walletManagerToggles.isRedesignedScreenEnabled) {
val walletManagerFacade = store.state.daggerGraphState
.get(DaggerGraphState::walletManagersFacade)
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
} else {
store.state.walletState.walletManagers
}
return walletManagers.mapNotNull {
val wallet = it.wallet
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
}
}
fun updateConfigManager(data: ScanResponse) {

View file

@ -0,0 +1,29 @@
package com.tangem.tap.domain.card
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.tap.domain.TangemSdkManager
// TODO: [REDACTED_JIRA]
internal class DefaultDerivePublicKeysUseCase(
private val tangemSdkManager: TangemSdkManager,
) : DerivePublicKeysUseCase {
override suspend fun invoke(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): Either<Unit, DerivationTaskResponse> {
tangemSdkManager.derivePublicKeys(cardId = cardId, derivations = derivations)
.doOnSuccess { return it.right() }
.doOnFailure { return Unit.left() }
return Unit.left()
}
}

View file

@ -8,6 +8,8 @@ import java.util.concurrent.CopyOnWriteArrayList
/**
[REDACTED_AUTHOR]
*/
// TODO: Delete with WalletFeatureToggles
@Deprecated(message = "Used only in old wallet screen")
class WarningMessagesManager {
private val warningsList = CopyOnWriteArrayList<WarningMessage>()
@ -73,7 +75,7 @@ class WarningMessagesManager {
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
messageResId = R.string.alert_developer_card,
// messageResId = R.string.alert_developer_card,
origin = WarningMessage.Origin.Local,
)
@ -85,7 +87,7 @@ class WarningMessagesManager {
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
messageResId = R.string.alert_card_signed_transactions,
// messageResId = R.string.alert_card_signed_transactions,
origin = WarningMessage.Origin.Local,
)
@ -96,8 +98,8 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.warning_important_security_info,
messageResId = R.string.warning_signed_tx_previously,
// titleResId = R.string.warning_important_security_info,
// messageResId = R.string.warning_signed_tx_previously,
origin = WarningMessage.Origin.Local,
buttonTextId = R.string.warning_button_learn_more,
titleFormatArg = "\u26A0",
@ -147,7 +149,7 @@ class WarningMessagesManager {
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
messageResId = R.string.alert_demo_message,
// messageResId = R.string.alert_demo_message,
origin = WarningMessage.Origin.Local,
)
@ -160,14 +162,14 @@ class WarningMessagesManager {
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
messageResId = R.string.warning_low_signatures_format,
// messageResId = R.string.warning_low_signatures_format,
origin = WarningMessage.Origin.Local,
messageFormatArg = remainingSignatures.toString(),
)
}
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
return warning.messageResId == R.string.alert_card_signed_transactions
}
// fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
// return warning.messageResId == R.string.alert_card_signed_transactions
// }
}
}

View file

@ -15,7 +15,10 @@ import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.*
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
@ -23,6 +26,7 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -34,8 +38,7 @@ internal object LegacyScanProcessor {
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse> {
return tangemSdkManager.scanProduct(
userTokensRepository,
cardId,
cardId = cardId,
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
)
}
@ -56,10 +59,7 @@ internal object LegacyScanProcessor {
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
val result = tangemSdkManager.scanProduct(
userTokensRepository = userTokensRepository,
cardId = cardId,
)
val result = tangemSdkManager.scanProduct(cardId)
store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result))

View file

@ -8,13 +8,13 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.domain.scanCard.chains.*
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
internal object UseCaseScanProcessor {

View file

@ -11,7 +11,6 @@ import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.setContext
@ -21,6 +20,7 @@ import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
import org.rekotlin.Store

View file

@ -8,13 +8,8 @@ import com.tangem.domain.card.repository.ScanCardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
import com.tangem.tap.domain.tokens.UserTokensRepository
internal class DefaultScanCardRepository(
// FIXME: The repository should not depend on another repository.
// But now we need to provide loadBlockchainsToDerive() to ScanProductTask and it's hard to move this method from
// UserTokensRepository.
private val userTokensRepository: UserTokensRepository,
private val tangemSdkManager: TangemSdkManager,
) : ScanCardRepository {
@ -27,7 +22,6 @@ internal class DefaultScanCardRepository(
when (
val result = tangemSdkManager.scanProduct(
cardId = cardId,
userTokensRepository = userTokensRepository,
allowsRequestAccessCodeFromRepository = allowRequestAccessCodeFromStorage,
)
) {

View file

@ -303,7 +303,7 @@ private class CreateWalletTangemWallet(
private fun getBlockchains(cardId: String, card: CardDTO): List<Blockchain> {
return when {
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains.toList()
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}

View file

@ -0,0 +1,159 @@
package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.domain.tokens.UserTokensStorageService
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
internal data class BlockchainToDerive(
val blockchain: Blockchain,
val derivationPath: DerivationPath?,
)
// FIXME: May be move to DI, currently unnecessary
internal class DerivationsFinder(
private val legacyTokensStore: UserTokensStorageService,
private val newTokensStore: UserTokensStore,
private val walletFeatureToggles: WalletFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend fun findBlockchainsToDerive(
card: CardDTO,
derivationStyleProvider: DerivationStyleProvider,
): Set<BlockchainToDerive> {
if (!card.settings.isHDWalletAllowed || card.wallets.isEmpty()) return emptySet()
val userWalletId = UserWalletIdBuilder.card(card).build() ?: return emptySet()
val derivationStyle = derivationStyleProvider.getDerivationStyle()
var blockchains = withContext(dispatchers.io) {
if (walletFeatureToggles.isRedesignedScreenEnabled) {
getBlockchainsNew(userWalletId)
} else {
getBlockchainsLegacy(userWalletId)
}
}
if (blockchains.isEmpty()) {
blockchains = if (DemoHelper.isDemoCardId(card.cardId)) {
getDemoBlockchains(derivationStyle)
} else {
getDefaultBlockchains(derivationStyle)
}
}
// we should generate second key for cardano
// because cardano address generation for wallet2 requires keys from 2 derivations
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
blockchains.addSecondCardanoDerivationIfPresent()
if (card.settings.isHDWalletAllowed) {
blockchains.addEthereumBlockchains(derivationStyle)
}
// pay attention to this
if (!card.useOldStyleDerivation) {
blockchains.removeUnnecessaryBlockchains()
}
return blockchains
}
private suspend fun getBlockchainsNew(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
val responseTokens = newTokensStore.getSyncOrNull(userWalletId)
?.tokens
?: return hashSetOf()
return responseTokens.asSequence()
.filter { it.contractAddress == null }
.mapNotNull { coin ->
val blockchain = Blockchain.fromNetworkId(coin.networkId) ?: return@mapNotNull null
val derivationPath = coin.derivationPath?.let(::DerivationPath)
BlockchainToDerive(blockchain, derivationPath)
}
.toMutableSet()
}
private fun getBlockchainsLegacy(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
val currencies = legacyTokensStore.getUserTokens(userWalletId.stringValue)
?.takeIf { it.isNotEmpty() }
?: return hashSetOf()
return currencies.asSequence()
.filterIsInstance<Currency.Blockchain>()
.map { coin ->
val blockchain = coin.blockchain
val derivationPath = coin.derivationPath?.let(::DerivationPath)
BlockchainToDerive(blockchain, derivationPath)
}
.toMutableSet()
}
// TODO: Move to user wallet config
private fun getDemoBlockchains(derivationStyle: DerivationStyle?): MutableSet<BlockchainToDerive> {
return DemoHelper.config.demoBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
}
// TODO: Move to user wallet config
private fun getDefaultBlockchains(derivationStyle: DerivationStyle?): MutableSet<BlockchainToDerive> {
val defaultBlockchains = setOf(Blockchain.Bitcoin, Blockchain.Ethereum)
return defaultBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
}
}
private fun MutableSet<BlockchainToDerive>.addEthereumBlockchains(derivationStyle: DerivationStyle?) {
val ethereumBlockchains = setOf(Blockchain.Ethereum, Blockchain.EthereumTestnet)
.mapToBlockchainsWithDerivations(derivationStyle)
addAll(ethereumBlockchains)
}
private fun MutableSet<BlockchainToDerive>.removeUnnecessaryBlockchains() {
val unnecessaryBlockchains = listOf(
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Polygon, Blockchain.PolygonTestnet,
Blockchain.RSK,
Blockchain.Fantom, Blockchain.FantomTestnet,
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
)
removeAll { it.blockchain in unnecessaryBlockchains }
}
private fun MutableSet<BlockchainToDerive>.addSecondCardanoDerivationIfPresent() {
val cardanoDerivation = this
.firstOrNull { it.blockchain == Blockchain.Cardano }
?.derivationPath
?: return
val secondCardanoBlockchain = BlockchainToDerive(
blockchain = Blockchain.Cardano,
derivationPath = CardanoUtils.extendedDerivationPath(cardanoDerivation),
)
add(secondCardanoBlockchain)
}
private fun Set<Blockchain>.mapToBlockchainsWithDerivations(
derivationStyle: DerivationStyle?,
): MutableSet<BlockchainToDerive> {
return mapTo(hashSetOf()) { blockchain ->
BlockchainToDerive(blockchain, blockchain.derivationPath(derivationStyle))
}
}

View file

@ -1,7 +1,5 @@
package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
@ -15,13 +13,11 @@ import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvDecoder
import com.tangem.crypto.CryptoUtils
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isExcluded
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.TwinsHelper
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.derivationStyleProvider
@ -35,16 +31,14 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.files.ReadFilesTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import kotlinx.coroutines.launch
import kotlin.collections.set
class ScanProductTask(
val card: Card? = null,
private val userTokensRepository: UserTokensRepository?,
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
internal class ScanProductTask(
private val card: Card?,
private val derivationsFinder: DerivationsFinder?,
override val allowsRequestAccessCodeFromRepository: Boolean = false,
) : CardSessionRunnable<ScanResponse> {
@ -63,14 +57,14 @@ class ScanProductTask(
val commandProcessor = when {
cardDto.isTangemTwins -> ScanTwinProcessor()
else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive)
else -> ScanWalletProcessor(derivationsFinder)
}
commandProcessor.proceed(cardDto, session) { processorResult ->
when (processorResult) {
is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
when (scanTaskResult) {
is CompletionResult.Success -> {
// it need because processorResult.data.card doesn't contains attestation result
// it needed because processorResult.data.card doesn't contains attestation result
// and CardWallet.derivedKeys
val processorScanResponseWithNewCard = processorResult.data.copy(
card = CardDTO(scanTaskResult.data),
@ -99,8 +93,7 @@ class ScanProductTask(
}
private class ScanWalletProcessor(
private val userTokensRepository: UserTokensRepository?,
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
private val derivationsFinder: DerivationsFinder?,
) : ProductCommandProcessor<ScanResponse> {
var primaryCard: PrimaryCard? = null
@ -244,128 +237,25 @@ private class ScanWalletProcessor(
}
}
private suspend fun getBlockchainsToDerive(
card: CardDTO,
derivationStyleProvider: DerivationStyleProvider,
): List<BlockchainNetwork> {
val userTokensRepository = userTokensRepository ?: return emptyList()
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(
card,
derivationStyleProvider.getDerivationStyle(),
)
.toMutableList()
.ifEmpty { getDefaultBlockchains(derivationStyleProvider) }
if (card.settings.isHDWalletAllowed) {
blockchainsToDerive += getEthereumBlockchains(derivationStyleProvider)
}
additionalBlockchainsToDerive?.let {
blockchainsToDerive += getAdditionalBlockchainToDerive(derivationStyleProvider, it)
}
// we should generate second key for cardano
// because cardano address generation for wallet2 requires keys from 2 derivations
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
val secondCardanoNetwork = blockchainsToDerive
.find { it.blockchain == Blockchain.Cardano }
?.let { getCardanoSecondNetwork(it) }
secondCardanoNetwork?.let { blockchainsToDerive.add(it) }
// pay attention to this
if (!card.useOldStyleDerivation) {
removeUnnecessaryBlockchains(blockchainsToDerive, derivationStyleProvider)
}
return blockchainsToDerive.distinct()
}
private fun getWalletProductType(card: CardDTO): ProductType {
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available) {
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
card.settings.isKeysImportAllowed
) {
ProductType.Wallet2
} else {
ProductType.Wallet
}
}
private fun getDefaultBlockchains(
derivationStyleProvider: DerivationStyleProvider,
): MutableList<BlockchainNetwork> {
return mutableListOf(
BlockchainNetwork(
blockchain = Blockchain.Bitcoin,
derivationStyleProvider = derivationStyleProvider,
),
BlockchainNetwork(
blockchain = Blockchain.Ethereum,
derivationStyleProvider = derivationStyleProvider,
),
)
}
private fun getEthereumBlockchains(derivationStyleProvider: DerivationStyleProvider): List<BlockchainNetwork> {
return listOf(
BlockchainNetwork(
blockchain = Blockchain.Ethereum,
derivationStyleProvider = derivationStyleProvider,
),
BlockchainNetwork(
blockchain = Blockchain.EthereumTestnet,
derivationStyleProvider = derivationStyleProvider,
),
)
}
private fun getAdditionalBlockchainToDerive(
derivationStyleProvider: DerivationStyleProvider,
collection: Collection<Blockchain>,
): List<BlockchainNetwork> {
return collection.map {
BlockchainNetwork(
blockchain = it,
derivationStyleProvider = derivationStyleProvider,
)
}
}
private fun getCardanoSecondNetwork(cardanoBlockchainNetwork: BlockchainNetwork): BlockchainNetwork? {
val cardanoStandardDerivation = cardanoBlockchainNetwork.derivationPath?.let { DerivationPath(it) }
?: return null
val cardanoPatchedDerivation = CardanoUtils.extendedDerivationPath(cardanoStandardDerivation)
return BlockchainNetwork(
blockchain = Blockchain.Cardano,
derivationPath = cardanoPatchedDerivation.rawPath,
tokens = emptyList(),
)
}
private fun removeUnnecessaryBlockchains(
blockchainsToDerive: MutableList<BlockchainNetwork>,
derivationStyleProvider: DerivationStyleProvider,
) {
blockchainsToDerive.removeAll(
listOf(
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Polygon, Blockchain.PolygonTestnet,
Blockchain.RSK,
Blockchain.Fantom, Blockchain.FantomTestnet,
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
).map {
BlockchainNetwork(
blockchain = it,
derivationStyleProvider = derivationStyleProvider,
)
},
)
}
private suspend fun collectDerivations(
card: CardDTO,
config: CardConfig,
derivationStyleProvider: DerivationStyleProvider,
): Map<ByteArrayKey, List<DerivationPath>> {
val blockchains = getBlockchainsToDerive(card, derivationStyleProvider)
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
val blockchains = derivationsFinder
?.findBlockchainsToDerive(card, derivationStyleProvider)
?: return derivations
blockchains.forEach { blockchain ->
val curve = config.primaryCurve(blockchain.blockchain)
@ -373,7 +263,7 @@ private class ScanWalletProcessor(
if (wallet.chainCode == null) return@forEach
val key = wallet.publicKey.toMapKey()
val path = blockchain.derivationPath?.let { DerivationPath(it) }
val path = blockchain.derivationPath
if (path != null) {
val addedDerivations = derivations[key]
if (addedDerivations != null) {
@ -383,6 +273,7 @@ private class ScanWalletProcessor(
}
}
}
return derivations
}
}

View file

@ -1,105 +0,0 @@
package com.tangem.tap.domain.tokens
import com.squareup.moshi.JsonAdapter
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.datasource.asset.AssetReader
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
class LoadAvailableCoinsService(
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
private val assetReader: AssetReader,
) {
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
MoshiConverter.networkMoshi.adapter(CurrenciesFromJson::class.java)
suspend fun getSupportedTokens(
isTestNet: Boolean,
supportedBlockchains: List<Blockchain>,
page: Int,
searchInput: String?,
): Result<LoadedCoins> {
if (isTestNet) {
return Result.Success(
LoadedCoins(
currencies = getTestnetCoins().filter(searchInput),
moreAvailable = false,
),
)
}
val offset = page * LOAD_PER_PAGE
return when (val result = loadCoins(supportedBlockchains, offset, searchInput)) {
is Result.Success -> {
val data = result.data
Result.Success(
LoadedCoins(
currencies = data.coins.map {
Currency.fromCoinResponse(currency = it, imageHost = data.imageHost)
},
moreAvailable = data.total > offset + LOAD_PER_PAGE,
),
)
}
is Result.Failure -> {
Result.Failure(result.error)
}
}
}
private suspend fun loadCoins(
supportedBlockchains: List<Blockchain>,
offset: Int,
searchInput: String?,
): Result<CoinsResponse> {
return withContext(dispatchers.io) {
runCatching {
tangemTechApi.getCoins(
networkIds = supportedBlockchains.joinToString(
separator = ",",
transform = Blockchain::toNetworkId,
),
active = true,
searchText = searchInput,
offset = offset,
limit = LOAD_PER_PAGE,
)
}.fold(
onSuccess = { Result.Success(it) },
onFailure = { Result.Failure(it) },
)
}
}
private fun getTestnetCoins(): List<Currency> {
val json = assetReader.readJson(FILE_NAME_TESTNET_COINS)
return currenciesAdapter.fromJson(json)!!.coins
.map { Currency.fromJsonObject(it) }
}
private fun List<Currency>.filter(searchInput: String?): List<Currency> {
if (searchInput.isNullOrBlank()) return this
return filter { currency ->
currency.symbol.contains(searchInput, ignoreCase = true) ||
currency.name.contains(searchInput, ignoreCase = true)
}
}
private companion object {
const val LOAD_PER_PAGE = 100
const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
}
}
data class LoadedCoins(
val currencies: List<Currency>,
val moreAvailable: Boolean,
)

View file

@ -1,13 +1,12 @@
package com.tangem.tap.domain.tokens
import android.content.Context
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.core.TangemSdkError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.files.AndroidFileReader
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.userwallets.UserWalletIdBuilder
@ -108,7 +107,8 @@ class UserTokensRepository(
return runCatching { tangemTechApi.getUserTokens(userWalletId) }
.fold(
onSuccess = { response ->
response.tokens
response.getOrThrow()
.tokens
.mapNotNull(Currency.Companion::fromTokenResponse)
.also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
.distinct()
@ -131,12 +131,12 @@ class UserTokensRepository(
// TODO("After adding DI") get dependencies by DI
fun init(
context: Context,
tangemTechService: TangemTechService,
networkConnectionManager: NetworkConnectionManager,
storageService: UserTokensStorageService,
): UserTokensRepository {
return UserTokensRepository(
storageService = UserTokensStorageService(fileReader = AndroidFileReader(context)),
storageService = storageService,
tangemTechApi = tangemTechService.api,
dispatchers = AppCoroutineDispatcherProvider(),
networkConnectionManager = networkConnectionManager,

View file

@ -1,9 +1,11 @@
package com.tangem.tap.domain.tokens
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.tangem.Log
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.files.AndroidFileReader
import com.tangem.datasource.files.FileReader
import com.tangem.tap.features.wallet.models.Currency
@ -30,5 +32,7 @@ class UserTokensStorageService(private val fileReader: FileReader) {
companion object {
private const val FILE_NAME_PREFIX_USER_TOKENS = "user_tokens"
private fun getFileNameForUserTokens(userId: String): String = "${FILE_NAME_PREFIX_USER_TOKENS}_$userId"
fun init(context: Context) = UserTokensStorageService(AndroidFileReader(context))
}
}

View file

@ -23,7 +23,7 @@ class FinalizeTwinTask(
PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
when (readResult) {
is CompletionResult.Success ->
ScanProductTask(readResult.data, null)
ScanProductTask(readResult.data, derivationsFinder = null)
.run(session, callback)
is CompletionResult.Failure ->
callback(CompletionResult.Failure(readResult.error))

View file

@ -3,25 +3,28 @@ package com.tangem.tap.domain.userWalletList.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.Provider
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
import com.tangem.tap.domain.userWalletList.utils.json.*
import com.tangem.tap.tangemSdkManager
private const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
fun UserWalletsListManager.Companion.provideBiometricImplementation(
context: Context,
tangemSdkManager: TangemSdkManager,
applicationContext: Context,
): UserWalletsListManager {
val moshi = Moshi.Builder()
.add(WalletDerivedKeysMapAdapter())
@ -38,15 +41,25 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation(
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = context,
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
),
)
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
),
keystoreManager = DelegatedKeystoreManager(
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
),
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
biometricManager = tangemSdkManager.biometricManager,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,

View file

@ -57,21 +57,11 @@ internal class BiometricUserWalletsListManager(
get() = state.value.userWallets.size
override suspend fun unlock(): CompletionResult<UserWallet> {
return unlockWithBiometryInternal()
.mapFailure { error ->
if (error is UserWalletsListError) {
error
} else {
UserWalletsListError.UnableToUnlockUserWallets(cause = error)
}
}
.map {
selectedUserWalletSync.guard {
throw UserWalletsListError.UnableToUnlockUserWallets(
cause = IllegalStateException("No user wallet selected"),
)
}
}
return unlockWithBiometryInternal().mapUnlockResult()
}
override suspend fun unlockAndSelect(selectedWalletId: UserWalletId): CompletionResult<UserWallet> {
return unlockWithBiometryInternal(selectedWalletId = selectedWalletId).mapUnlockResult()
}
override fun lock() {
@ -141,7 +131,7 @@ internal class BiometricUserWalletsListManager(
return sensitiveInformationRepository.delete(idsToRemove)
.flatMap { publicInformationRepository.delete(idsToRemove) }
.flatMap { keysRepository.delete(idsToRemove) }
.map { keysRepository.delete(idsToRemove) }
.map {
state.update { prevState ->
val newUserWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
@ -158,7 +148,7 @@ internal class BiometricUserWalletsListManager(
override suspend fun clear(): CompletionResult<Unit> {
return sensitiveInformationRepository.clear()
.flatMap { publicInformationRepository.clear() }
.flatMap { keysRepository.clear() }
.map { keysRepository.clear() }
.map {
selectedUserWalletRepository.set(null)
lock()
@ -198,7 +188,7 @@ internal class BiometricUserWalletsListManager(
}
}
private suspend fun unlockWithBiometryInternal(): CompletionResult<Unit> {
private suspend fun unlockWithBiometryInternal(selectedWalletId: UserWalletId? = null): CompletionResult<Unit> {
return keysRepository.getAll()
.map { keys ->
state.update { prevState ->
@ -207,7 +197,7 @@ internal class BiometricUserWalletsListManager(
)
}
}
.flatMap { loadModels() }
.flatMap { loadModels(selectedWalletId = selectedWalletId) }
.map {
state.update { prevState ->
val hasLockedUserWallets = prevState.userWallets.any { it.isLocked }
@ -216,6 +206,24 @@ internal class BiometricUserWalletsListManager(
}
}
private fun CompletionResult<Unit>.mapUnlockResult(): CompletionResult<UserWallet> {
return this
.mapFailure { error ->
if (error is UserWalletsListError) {
error
} else {
UserWalletsListError.UnableToUnlockUserWallets(cause = error)
}
}
.map {
selectedUserWalletSync.guard {
throw UserWalletsListError.UnableToUnlockUserWallets(
cause = IllegalStateException("No user wallet selected"),
)
}
}
}
private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult<ByteArray?> {
val encryptionKey = userWallet.scanResponse.card.encryptionKey
?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
@ -237,7 +245,7 @@ internal class BiometricUserWalletsListManager(
}
}
private suspend fun loadModels(): CompletionResult<Unit> {
private suspend fun loadModels(selectedWalletId: UserWalletId? = null): CompletionResult<Unit> {
return getSavedUserWallets()
.map { userWallets ->
if (userWallets.isNotEmpty()) {
@ -247,7 +255,7 @@ internal class BiometricUserWalletsListManager(
prevState.copy(
userWallets = wallets,
selectedUserWalletId = findOrSetSelectedUserWalletId(
prevSelectedWalletId = prevState.selectedUserWalletId,
prevSelectedWalletId = selectedWalletId ?: prevState.selectedUserWalletId,
userWallets = wallets,
),
)

View file

@ -0,0 +1,18 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.Provider
import com.tangem.common.authentication.KeystoreManager
import javax.crypto.SecretKey
internal class DelegatedKeystoreManager(
private val keystoreManagerProvider: Provider<KeystoreManager>,
) : KeystoreManager {
override suspend fun authenticateAndGetKey(keyAlias: String): SecretKey? {
return keystoreManagerProvider().authenticateAndGetKey(keyAlias)
}
override suspend fun storeKey(keyAlias: String, key: SecretKey) {
keystoreManagerProvider().storeKey(keyAlias, key)
}
}

View file

@ -23,15 +23,13 @@ internal interface UserWalletsKeysRepository {
/**
* Delete encryption keys for user wallets. Biometric authentication not required
* @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted
* @return [CompletionResult] of operation
* */
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun delete(userWalletsIds: List<UserWalletId>)
/**
* Clear all encryption keys for user wallets. Biometric authentication not required
* @return [CompletionResult] of operation
* */
suspend fun clear(): CompletionResult<Unit>
suspend fun clear()
/**
* Determine if the user has saved user wallets

View file

@ -0,0 +1,32 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.Provider
import com.tangem.common.services.secure.SecureStorage
/**
* A decorator for [SecureStorage] that facilitates data migration between two storages.
*
* @property featureStorage The primary storage, which will eventually contain all user data.
* @property cardSdkStorageProvider The SDK's storage where user data might have been previously stored.
*/
internal class UserWalletsKeysStoreDecorator(
private val featureStorage: SecureStorage,
private val cardSdkStorageProvider: Provider<SecureStorage>,
) : SecureStorage by featureStorage {
override fun delete(account: String) {
featureStorage.delete(account)
cardSdkStorageProvider().delete(account)
}
override fun get(account: String): ByteArray? {
var data = featureStorage.get(account)
if (data == null) {
data = cardSdkStorageProvider().get(account) ?: return null
featureStorage.store(data, account)
}
return data
}
}

View file

@ -4,8 +4,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.common.*
import com.tangem.common.biometric.BiometricManager
import com.tangem.common.biometric.BiometricStorage
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListError
@ -18,13 +17,10 @@ import kotlinx.coroutines.withContext
internal class BiometricUserWalletsKeysRepository(
moshi: Moshi,
biometricManager: BiometricManager,
private val authenticatedStorage: AuthenticatedStorage,
private val secureStorage: SecureStorage,
) : UserWalletsKeysRepository {
private val biometricStorage = BiometricStorage(
biometricManager = biometricManager,
secureStorage = secureStorage,
)
private val encryptionKeyAdapter: JsonAdapter<UserWalletEncryptionKey> = moshi.adapter(
UserWalletEncryptionKey::class.java,
)
@ -37,13 +33,13 @@ internal class BiometricUserWalletsKeysRepository(
getAllInternal()
.mapFailure { error ->
when (error) {
is TangemSdkError.BiometricsAuthenticationLockout ->
is TangemSdkError.AuthenticationLockout ->
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = false)
is TangemSdkError.BiometricsAuthenticationPermanentLockout ->
is TangemSdkError.AuthenticationPermanentLockout ->
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
is TangemSdkError.BiometricCryptographyKeyInvalidated ->
is TangemSdkError.KeystoreInvalidated ->
UserWalletsListError.EncryptionKeyInvalidated
is TangemSdkError.BiometricsUnavailable ->
is TangemSdkError.AuthenticationUnavailable ->
UserWalletsListError.BiometricsAuthenticationDisabled
else -> error
}
@ -57,26 +53,24 @@ internal class BiometricUserWalletsKeysRepository(
}
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
override suspend fun delete(userWalletsIds: List<UserWalletId>) {
return withContext(Dispatchers.IO) {
userWalletsIds.map { userWalletId ->
userWalletsIds.forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
.fold()
.map { deleteUserWalletsIds(userWalletsIds) }
deleteUserWalletsIds(userWalletsIds)
}
}
override suspend fun clear(): CompletionResult<Unit> {
override suspend fun clear() {
return withContext(Dispatchers.IO) {
getUserWalletsIds()
.map { userWalletId ->
.forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
.fold()
.map {
clearUserWalletsIds()
}
clearUserWalletsIds()
}
}
@ -89,35 +83,20 @@ internal class BiometricUserWalletsKeysRepository(
private suspend fun getAllInternal(): CompletionResult<List<UserWalletEncryptionKey>> {
return getUserWalletsIds()
.map { userWalletId ->
// It is possible to request multiple user wallet keys from biometric storage because
// the biometric cryptography key has an expiration time.
// If this operation runs more than that expiration time, then the user will have to re-authorize
// to receive all user wallets encryption keys
getEncryptionKey(userWalletId)
.flatMapOnFailure { error ->
when (error) {
is TangemSdkError.InvalidBiometricCryptographyKey,
is TangemSdkError.BiometricCryptographyOperationFailed,
-> {
// These errors can be skipped as the user has the option to re-save their wallets
// in case they occur
CompletionResult.Success(data = null)
}
else -> CompletionResult.Failure(error)
}
}
.doOnFailure { error ->
when (error) {
is TangemSdkError.UserCanceledBiometricsAuthentication -> {
is TangemSdkError.UserCanceledAuthentication -> {
// If the user cancels biometric authentication, then cancel operation with error
return CompletionResult.Failure(error)
}
is TangemSdkError.BiometricCryptographyKeyInvalidated -> {
is TangemSdkError.KeystoreInvalidated -> {
// If the biometric cryptography key was invalidated,
// then delete all user wallets encryption keys and cancel operation with error
getUserWalletsIds().forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
return CompletionResult.Failure(error)
}
}
@ -129,20 +108,22 @@ internal class BiometricUserWalletsKeysRepository(
}
private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult<UserWalletEncryptionKey?> {
return biometricStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name)
return catching { authenticatedStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name) }
.map { it.decodeToKey() }
}
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
return biometricStorage.store(
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
data = encryptionKey.encode(),
)
return catching {
authenticatedStorage.store(
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
data = encryptionKey.encode(),
)
}
.map { storeUserWalletId(encryptionKey.walletId) }
}
private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult<Unit> {
return biometricStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
private fun deleteEncryptionKey(userWalletId: UserWalletId) {
return authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
}
private suspend fun getUserWalletsIds(): List<UserWalletId> {

View file

@ -1,18 +1,17 @@
package com.tangem.tap.domain.walletStores.repository.implementation
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.common.doOnSuccess
import com.tangem.common.mapFailure
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.makeWalletManagerForApp
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.models.UserWallet
@ -70,7 +69,7 @@ internal class DefaultWalletManagersRepository(
}
val derivationParams = getDerivationParams(
derivationPath = blockchainNetwork?.derivationPath,
card = scanResponse.card,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
val walletManager = blockchain?.let {
@ -209,17 +208,16 @@ internal class DefaultWalletManagersRepository(
}
}
private fun getDerivationParams(derivationPath: String?, card: CardDTO): DerivationParams? {
return derivationPath?.let {
DerivationParams.Custom(
path = DerivationPath(it),
)
} ?: if (!card.settings.isHDWalletAllowed) {
null
} else if (card.useOldStyleDerivation) {
DerivationParams.Default(DerivationStyle.LEGACY)
private fun getDerivationParams(
derivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): DerivationParams? {
val derivationStyle = derivationStyleProvider.getDerivationStyle() ?: return null
return if (derivationPath == null) {
DerivationParams.Default(derivationStyle)
} else {
DerivationParams.Default(DerivationStyle.NEW)
DerivationParams.Custom(DerivationPath(derivationPath))
}
}
}