Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-25 22:28:34 +08:00
parent 53926e0843
commit 4d8930951c
6 changed files with 122 additions and 43 deletions

View file

@ -25,12 +25,13 @@ dependencies {
}
/** Other libraries */
implementation(deps.reKotlin)
implementation(deps.arrow.core)
implementation(deps.jodatime)
implementation(deps.kotlin.coroutines)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.reKotlin)
implementation(deps.timber)
implementation(deps.kotlin.coroutines)
implementation(deps.jodatime)
/** Testing libraries */
testImplementation(deps.test.junit)

View file

@ -1,15 +1,17 @@
package com.tangem.domain.walletmanager
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.userwallet.UserWalletsStore
@ -24,6 +26,7 @@ import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.walletmanager.utils.*
import com.tangem.domain.walletmanager.utils.WalletManagerFactory
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -318,6 +321,29 @@ class DefaultWalletManagersFacade(
return walletManagersStore.getAll(userWalletId)
}
override suspend fun validateSignatureCount(
userWalletId: UserWalletId,
network: Network,
signedHashes: Int,
): Either<Throwable, Unit> {
return either {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = Blockchain.fromId(network.id.value),
derivationPath = network.derivationPath.value,
)
val validator = ensureNotNull(walletManager as? SignatureCountValidator) {
raise(IllegalStateException("Wallet manager is not a SignatureCountValidator"))
}
when (val result = validator.validateSignatureCount(signedHashes)) {
is SimpleResult.Failure -> raise(result.error)
is SimpleResult.Success -> Unit.right()
}
}
}
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return

View file

@ -1,5 +1,6 @@
package com.tangem.domain.walletmanager
import arrow.core.Either
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
@ -113,4 +114,10 @@ interface WalletManagersFacade {
@Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>>
suspend fun validateSignatureCount(
userWalletId: UserWalletId,
network: Network,
signedHashes: Int,
): Either<Throwable, Unit>
}

View file

@ -0,0 +1,50 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@ViewModelScoped
class HasSingleWalletSignedHashesUseCase @Inject constructor(
private val cardRepository: CardRepository,
private val walletManagersFacade: WalletManagersFacade,
) {
operator fun invoke(userWallet: UserWallet, network: Network): Flow<Boolean> {
return cardRepository.wasCardScanned(cardId = userWallet.cardId)
.map { wasCardScanned ->
if (wasCardScanned || !userWallet.isCorrectCardType()) return@map false
if (!userWallet.scanResponse.cardTypesResolver.hasWalletSignedHashes()) {
cardRepository.setCardWasScanned(cardId = userWallet.cardId)
return@map false
}
return@map walletManagersFacade.validateSignatureCount(
userWalletId = userWallet.walletId,
network = network,
signedHashes = userWallet.scanResponse.card.wallets.firstOrNull()?.totalSignedHashes ?: 0,
)
.fold(
ifLeft = { true },
ifRight = {
cardRepository.setCardWasScanned(cardId = userWallet.cardId)
false
},
)
}
}
private fun UserWallet.isCorrectCardType(): Boolean {
return with(scanResponse.cardTypesResolver) {
!DemoConfig().isDemoCardId(cardId) && isReleaseFirmwareType() && !isMultiwalletAllowed() && !isTangemTwins()
}
}
}

View file

@ -1,29 +1,30 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import arrow.core.Either
import com.tangem.domain.card.WasCardScannedUseCase
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase
import com.tangem.domain.tokens.error.GetCurrenciesError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.HasSingleWalletSignedHashesUseCase
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOf
/**
* Wallet notifications list factory
*
* @property isDemoCardUseCase use case that checks if card is demo
* @property isReadyToShowRateAppUseCase use case that checks if card is user already rate app
* @property wasCardScannedUseCase use case that checks if card was scanned
* @property isNeedToBackupUseCase use case that checks if wallet need backup cards
* @property clickIntents screen click intents
*
@ -32,38 +33,50 @@ import kotlinx.coroutines.flow.conflate
internal class WalletNotificationsListFactory(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val wasCardScannedUseCase: WasCardScannedUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
private val clickIntents: WalletClickIntents,
) {
private var readyForRateAppNotification = false
fun create(
selectedWalletId: UserWalletId,
cardTypesResolver: CardTypesResolver,
selectedWallet: UserWallet,
cryptoCurrencyList: List<CryptoCurrencyStatus>,
): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = selectedWallet.scanResponse.cardTypesResolver
return combine(
flow = wasCardScannedUseCase(cardTypesResolver.getCardId()).conflate(),
flow = hasSingleWalletSignedHashesFlow(selectedWallet, cryptoCurrencyList),
flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(selectedWalletId).conflate(),
flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWalletId).conflate(),
) { wasCardScanned, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies ->
flow3 = isNeedToBackupUseCase(selectedWallet.walletId).conflate(),
flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWallet.walletId).conflate(),
) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies ->
readyForRateAppNotification = true
buildList {
addCriticalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies)
addWarningNotifications(cardTypesResolver, cryptoCurrencyList, wasCardScanned, isNeedToBackup)
addWarningNotifications(cardTypesResolver, cryptoCurrencyList, hasSignedHashes, isNeedToBackup)
addRateTheAppNotification(isReadyToShowRating)
}.toImmutableList()
}
}
private fun hasSingleWalletSignedHashesFlow(
selectedWallet: UserWallet,
cryptoCurrencyList: List<CryptoCurrencyStatus>,
): Flow<Boolean> {
return if (selectedWallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
flowOf(value = false)
} else {
val network = requireNotNull(cryptoCurrencyList.firstOrNull()?.currency?.network)
hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = network).conflate()
}
}
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf(
element = WalletNotification.Critical.DevCard,
@ -97,7 +110,7 @@ internal class WalletNotificationsListFactory(
private fun MutableList<WalletNotification>.addWarningNotifications(
cardTypesResolver: CardTypesResolver,
cryptoCurrencyList: List<CryptoCurrencyStatus>,
wasCardScanned: Boolean,
hasSignedHashes: Boolean,
isNeedToBackup: Boolean,
) {
addIf(
@ -112,7 +125,6 @@ internal class WalletNotificationsListFactory(
condition = cardTypesResolver.isTestCard(),
)
val isDemo = isDemoCardUseCase(cardId = cardTypesResolver.getCardId())
if (cardTypesResolver.isMultiwalletAllowed()) {
addIf(
element = WalletNotification.Warning.SomeNetworksUnreachable,
@ -130,11 +142,7 @@ internal class WalletNotificationsListFactory(
element = WalletNotification.Warning.NumberOfSignedHashesIncorrect(
onCloseClick = clickIntents::onSignedHashesNotificationCloseClick,
),
condition = checkSignedHashes(
cardTypesResolver = cardTypesResolver,
isDemo = isDemo,
wasCardScanned = wasCardScanned,
),
condition = hasSignedHashes,
)
}
}
@ -195,18 +203,6 @@ internal class WalletNotificationsListFactory(
}
}
/**
* Warning is being shown for single wallet cards only
*/
private fun checkSignedHashes(
cardTypesResolver: CardTypesResolver,
isDemo: Boolean,
wasCardScanned: Boolean,
): Boolean {
return cardTypesResolver.isReleaseFirmwareType() && !cardTypesResolver.isTangemTwins() &&
cardTypesResolver.hasWalletSignedHashes() && !isDemo && !wasCardScanned
}
private companion object {
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}

View file

@ -31,7 +31,6 @@ import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.card.WasCardScannedUseCase
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.cardTypesResolver
@ -60,6 +59,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.domain.HasSingleWalletSignedHashesUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletError
import com.tangem.feature.wallet.presentation.wallet.state.*
@ -127,7 +127,7 @@ internal class WalletViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler,
wasCardScannedUseCase: WasCardScannedUseCase,
hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
isNeedToBackupUseCase: IsNeedToBackupUseCase,
getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
@ -140,11 +140,11 @@ internal class WalletViewModel @Inject constructor(
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val notificationsListFactory = WalletNotificationsListFactory(
wasCardScannedUseCase = wasCardScannedUseCase,
isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase,
isDemoCardUseCase = isDemoCardUseCase,
isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase,
isNeedToBackupUseCase = isNeedToBackupUseCase,
getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase,
hasSingleWalletSignedHashesUseCase = hasSingleWalletSignedHashesUseCase,
clickIntents = this,
)
@ -1316,8 +1316,7 @@ internal class WalletViewModel @Inject constructor(
private fun updateNotifications(index: Int, tokenList: TokenList? = null) {
notificationsListFactory.create(
selectedWalletId = getWallet(index).walletId,
cardTypesResolver = getCardTypeResolver(index = index),
selectedWallet = getWallet(index),
cryptoCurrencyList = if (tokenList != null) {
when (tokenList) {
is TokenList.GroupedByNetwork -> {