Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 11:49:12 +04:00
parent 6558188794
commit bf3a15c114
11 changed files with 46 additions and 801 deletions

View file

@ -62,8 +62,6 @@ import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.tokens.UserTokensStorageService
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
@ -120,15 +118,6 @@ val walletStoresManager by lazy {
appCurrencyProvider = { store.state.globalState.appCurrency },
)
}
val walletCurrenciesManager by lazy {
WalletCurrenciesManager.provideDefaultImplementation(
userTokensRepository = userTokensRepository,
walletStoresRepository = walletStoresRepository,
walletManagersRepository = walletManagersRepository,
walletAmountsRepository = walletAmountsRepository,
appCurrencyProvider = { store.state.globalState.appCurrency },
)
}
@HiltAndroidApp
internal class TapApplication : Application(), ImageLoaderFactory {

View file

@ -1,87 +0,0 @@
package com.tangem.tap.domain.walletCurrencies
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.features.wallet.models.Currency
interface WalletCurrenciesManager {
/**
* Update [UserWallet] currencies with same blockchain as provided [Currency] blockchain
* [UserWallet] currencies updates can be observed
* with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll]
* or [com.tangem.tap.domain.walletStores.WalletStoresManager.get]
*
* @param userWallet [UserWallet] which currencies will be updated
* @param currency [Currency] to find other currencies to update
*
* @return [CompletionResult] of operation
* */
suspend fun update(userWallet: UserWallet, currency: Currency): CompletionResult<Unit>
/**
* Add list of [Currency] to [UserWallet].
* [UserWallet] currencies updates can be observed
* with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll]
* or [com.tangem.tap.domain.walletStores.WalletStoresManager.get]
*
* @param userWallet [UserWallet] to add currencies
* @param currenciesToAdd list of [Currency] to add [UserWallet]
*
* @return [CompletionResult] of operation
* */
suspend fun addCurrencies(userWallet: UserWallet, currenciesToAdd: List<Currency>): CompletionResult<Unit>
/**
* Remove [Currency] from [UserWallet]
* [UserWallet] currencies updates can be observed
* with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll]
* or [com.tangem.tap.domain.walletStores.WalletStoresManager.get]
*
* @param userWallet [UserWallet] which currency will be removed
* @param currencyToRemove [Currency] to remove from [UserWallet]
*
* @return [CompletionResult] of operation
* */
suspend fun removeCurrency(userWallet: UserWallet, currencyToRemove: Currency): CompletionResult<Unit>
/**
* Remove list of [Currency] from [UserWallet]
* [UserWallet] currencies updates can be observed
* with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll]
* or [com.tangem.tap.domain.walletStores.WalletStoresManager.get]
*
* @param userWallet [UserWallet] which currency will be removed
* @param currenciesToRemove list of [Currency] to remove from [UserWallet]
*
* @return [CompletionResult] of operation
* */
suspend fun removeCurrencies(userWallet: UserWallet, currenciesToRemove: List<Currency>): CompletionResult<Unit>
/**
* Add a callback [Listener]
*
* @param listener The callback that will add
*/
fun addListener(listener: Listener)
/**
* Remove a callback [Listener]
*
* @param listener The callback that will removed
*/
fun removeListener(listener: Listener)
/**
* Interface definition for a callbacks
*/
interface Listener {
fun willUpdate(userWallet: UserWallet, currency: Currency) {}
fun didUpdate(userWallet: UserWallet, currency: Currency) {}
fun willCurrenciesAdd(userWallet: UserWallet, currenciesToAdd: List<Currency>) {}
fun willCurrenciesRemove(userWallet: UserWallet, currenciesToRemove: List<Currency>) {}
fun willCurrencyRemove(userWallet: UserWallet, currencyToRemove: Currency) {}
}
// For provider
companion object
}

View file

@ -1,25 +0,0 @@
package com.tangem.tap.domain.walletCurrencies.di
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletCurrencies.implementation.DefaultWalletCurrenciesManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
fun WalletCurrenciesManager.Companion.provideDefaultImplementation(
userTokensRepository: UserTokensRepository,
walletStoresRepository: WalletStoresRepository,
walletAmountsRepository: WalletAmountsRepository,
walletManagersRepository: WalletManagersRepository,
appCurrencyProvider: () -> FiatCurrency,
): WalletCurrenciesManager {
return DefaultWalletCurrenciesManager(
userTokensRepository = userTokensRepository,
walletStoresRepository = walletStoresRepository,
walletAmountsRepository = walletAmountsRepository,
walletManagersRepository = walletManagersRepository,
appCurrencyProvider = appCurrencyProvider,
)
}

View file

@ -1,261 +0,0 @@
package com.tangem.tap.domain.walletCurrencies.implementation
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.*
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.model.builders.WalletStoreBuilder
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
internal class DefaultWalletCurrenciesManager(
private val userTokensRepository: UserTokensRepository,
private val walletStoresRepository: WalletStoresRepository,
private val walletAmountsRepository: WalletAmountsRepository,
private val walletManagersRepository: WalletManagersRepository,
private val appCurrencyProvider: () -> FiatCurrency,
) : WalletCurrenciesManager {
private val listeners = mutableListOf<WalletCurrenciesManager.Listener>()
override suspend fun update(userWallet: UserWallet, currency: Currency): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
listeners.forEach { it.willUpdate(userWallet, currency) }
val walletStore = walletStoresRepository.getSync(userWallet.walletId)
.find {
it.blockchain == currency.blockchain &&
it.derivationPath?.rawPath == currency.derivationPath
}
val updateResult = if (walletStore == null) {
CompletionResult.Success(Unit)
} else {
walletAmountsRepository.updateAmountsForWalletStore(
walletStore = walletStore,
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
)
}
listeners.forEach { it.didUpdate(userWallet, currency) }
updateResult
}
override suspend fun addCurrencies(
userWallet: UserWallet,
currenciesToAdd: List<Currency>,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
if (currenciesToAdd.isEmpty()) {
return@withContext CompletionResult.Success(Unit)
}
val card = userWallet.scanResponse.card
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(
userWallet.scanResponse.derivationStyleProvider,
)
listeners.forEach { it.willCurrenciesAdd(userWallet, currenciesToAddWithMissingBlockchains) }
updateWalletStores(
userWallet = userWallet,
blockchainNetworks = currenciesToAddWithMissingBlockchains
.toBlockchainNetworks()
.addSameBlockchainTokens(userWallet.walletId),
)
.map {
saveUserCurrencies(card, getSavedCurrencies(userWallet.walletId))
}
.flatMap {
updateWalletStoresAmounts(
userWallet = userWallet,
updatedCurrencies = currenciesToAddWithMissingBlockchains,
)
}
}
override suspend fun removeCurrencies(
userWallet: UserWallet,
currenciesToRemove: List<Currency>,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
if (currenciesToRemove.isEmpty()) {
return@withContext CompletionResult.Success(Unit)
}
listeners.forEach { it.willCurrenciesRemove(userWallet, currenciesToRemove) }
val card = userWallet.scanResponse.card
val remainingCurrencies = getSavedCurrencies(userWallet.walletId)
.filter { it !in currenciesToRemove }
val remainingBlockchains = remainingCurrencies
.filterIsInstance<Currency.Blockchain>()
walletStoresRepository.deleteDifference(userWallet.walletId, remainingBlockchains)
.flatMap {
updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks())
}
.doOnResult {
saveUserCurrencies(card, remainingCurrencies)
}
}
override suspend fun removeCurrency(userWallet: UserWallet, currencyToRemove: Currency): CompletionResult<Unit> {
listeners.forEach { it.willCurrencyRemove(userWallet, currencyToRemove) }
return removeCurrencies(userWallet, listOf(currencyToRemove))
}
override fun addListener(listener: WalletCurrenciesManager.Listener) {
listeners.add(listener)
}
override fun removeListener(listener: WalletCurrenciesManager.Listener) {
listeners.remove(listener)
}
private suspend fun getSavedCurrencies(userWalletId: UserWalletId): List<Currency> {
return withContext(Dispatchers.Default) {
walletStoresRepository.getSync(userWalletId)
.flatMap { walletStore ->
walletStore.walletsData.map { it.currency }
}
}
}
private suspend fun saveUserCurrencies(card: CardDTO, currencies: List<Currency>) {
withContext(Dispatchers.IO) {
userTokensRepository.saveUserTokens(
card = card,
tokens = currencies,
)
}
}
// TODO: Need refactoring
private suspend fun List<BlockchainNetwork>.addSameBlockchainTokens(
userWalletId: UserWalletId,
): List<BlockchainNetwork> {
val networks = arrayListOf<BlockchainNetwork>()
val savedWalletStores = withContext(Dispatchers.Default) {
walletStoresRepository.getSync(userWalletId)
}
this.forEach { network ->
val walletStore = savedWalletStores.firstOrNull {
it.blockchain == network.blockchain && it.derivationPath?.rawPath == network.derivationPath
}
if (walletStore != null) {
val tokens = walletStore.walletsData
.asSequence()
.map { it.currency }
.filterIsInstance<Currency.Token>()
.map { it.token }
.toList()
networks.add(network.copy(tokens = tokens + network.tokens.toSet()))
} else {
networks.add(network)
}
}
return networks
}
private fun List<Currency>.addMissingBlockchainsIfNeeded(
derivationStyleProvider: DerivationStyleProvider,
): List<Currency> {
if (this.isEmpty()) return this
val currencies = this.asSequence()
return currencies
.groupBy { currency ->
findBlockchainCurrency(currency, currencies, derivationStyleProvider.getDerivationStyle())
}
.mapValues { (blockchainCurrency, blockchainCurrencies) ->
findBlockchainTokens(blockchainCurrency, blockchainCurrencies)
}
.flatMap { (blockchainCurrency, blockchainTokens) ->
arrayListOf(blockchainCurrency) + blockchainTokens
}
}
private fun findBlockchainCurrency(
currency: Currency,
currencies: Sequence<Currency>,
cardDerivationStyle: DerivationStyle?,
): Currency.Blockchain {
return currencies
.filterIsInstance<Currency.Blockchain>()
.firstOrNull {
it.blockchain == currency.blockchain &&
it.derivationPath == currency.derivationPath
}
?: Currency.Blockchain(
blockchain = currency.blockchain,
derivationPath = currency.derivationPath
?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath,
)
}
private fun findBlockchainTokens(
blockchainCurrency: Currency.Blockchain,
blockchainCurrencies: List<Currency>,
): List<Currency.Token> {
return blockchainCurrencies
.filterIsInstance<Currency.Token>()
.map { token ->
token.copy(
derivationPath = token.derivationPath ?: blockchainCurrency.derivationPath,
)
}
}
private suspend fun updateWalletStores(
userWallet: UserWallet,
blockchainNetworks: List<BlockchainNetwork>,
): CompletionResult<Unit> {
return blockchainNetworks
.map { blockchainNetwork ->
walletManagersRepository.findOrMakeMultiCurrencyWalletManager(
userWallet = userWallet,
blockchainNetwork = blockchainNetwork,
)
.flatMap { walletManager ->
walletStoresRepository.storeOrUpdate(
userWalletId = userWallet.walletId,
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
.walletManager(walletManager)
.build(),
)
}
}
.fold()
}
private suspend fun updateWalletStoresAmounts(
userWallet: UserWallet,
updatedCurrencies: List<Currency>,
): CompletionResult<Unit> {
val updatedBlockchains = updatedCurrencies
.filterIsInstance<Currency.Blockchain>()
val updatedWalletStores = walletStoresRepository.get(userWallet.walletId)
.firstOrNull()
?.filter { it.blockchainWalletData.currency in updatedBlockchains }
?: return CompletionResult.Success(Unit)
return walletAmountsRepository.updateAmountsForWalletStores(
walletStores = updatedWalletStores,
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
)
}
}

View file

@ -6,9 +6,7 @@ import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.TangemError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.flatMap
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.configs.CardConfig
@ -18,12 +16,9 @@ import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
@ -32,10 +27,11 @@ import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* Default implementation of custom token interactor
@ -178,70 +174,35 @@ class DefaultCustomTokenInteractor(
}
private suspend fun submitAdd(userWallet: UserWallet, currency: Currency) {
val cryptoCurrencyFactory = CryptoCurrencyFactory()
val scanResponse = userWallet.scanResponse
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
val userWalletId = userWallet.walletId
if (walletFeatureToggles.isRedesignedScreenEnabled) {
val cryptoCurrencyFactory = CryptoCurrencyFactory()
submitNewAdd(
userWalletId = userWallet.walletId,
updatedScanResponse = scanResponse,
currencyList = listOfNotNull(
when (currency) {
is Currency.Blockchain -> {
cryptoCurrencyFactory.createCoin(
blockchain = currency.blockchain,
extraDerivationPath = currency.derivationPath,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
}
is Currency.Token -> {
cryptoCurrencyFactory.createToken(
sdkToken = currency.token,
blockchain = currency.blockchain,
extraDerivationPath = currency.derivationPath,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
}
},
),
)
} else {
submitLegacyAdd(scanResponse = scanResponse, currency = currency)
}
}
private suspend fun submitLegacyAdd(scanResponse: ScanResponse, currency: Currency) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add currencies, no user wallet selected")
return
}
userWalletsListManager.update(
userWalletId = selectedUserWallet.walletId,
update = { userWallet -> userWallet.copy(scanResponse = scanResponse) },
val currencyList = listOfNotNull(
when (currency) {
is Currency.Blockchain -> {
cryptoCurrencyFactory.createCoin(
blockchain = currency.blockchain,
extraDerivationPath = currency.derivationPath,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
}
is Currency.Token -> {
cryptoCurrencyFactory.createToken(
sdkToken = currency.token,
blockchain = currency.blockchain,
extraDerivationPath = currency.derivationPath,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
}
},
)
.flatMap { updatedUserWallet ->
walletCurrenciesManager.addCurrencies(
userWallet = updatedUserWallet,
currenciesToAdd = listOf(currency),
)
}
}
private fun submitNewAdd(
userWalletId: UserWalletId,
updatedScanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,
) {
scope.launch {
userWalletsListManager.update(
userWalletId = userWalletId,
update = { it.copy(scanResponse = updatedScanResponse) },
)
addCryptoCurrenciesUseCase(userWalletId, currencyList)
userWalletsListManager.update(userWalletId) {
it.copy(scanResponse = scanResponse)
}
addCryptoCurrenciesUseCase(userWalletId, currencyList)
}
}

View file

@ -11,7 +11,6 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
@ -39,20 +38,15 @@ import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
import java.util.EnumSet
/**
@ -277,9 +271,6 @@ private fun sendTransaction(
Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
dispatch(NavigationAction.PopBackTo())
}
scope.launch(Dispatchers.IO) {
updateAfterTransaction(walletManager)
}
}
is SimpleResult.Failure -> {
updateFeedbackManagerInfo(
@ -418,32 +409,4 @@ private fun updateWarnings(dispatch: (Action) -> Unit) {
val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain))
dispatch(SendAction.Warnings.Set(warnings))
}
private suspend fun updateAfterTransaction(walletManager: WalletManager) {
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
updateWalletsLegacy(walletManager)
}
}
private suspend fun updateWalletsLegacy(walletManager: WalletManager) {
updateWallet(walletManager)
delay(timeMillis = 11000) // more than 10000 to avoid throttling
updateWallet(walletManager)
}
private suspend fun updateWallet(walletManager: WalletManager) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to update wallet, no user wallet selected")
return
}
val wallet = walletManager.wallet
walletCurrenciesManager.update(
userWallet = selectedUserWallet,
currency = Currency.Blockchain(
blockchain = wallet.blockchain,
derivationPath = wallet.publicKey.derivationPath?.rawPath,
),
)
}

View file

@ -11,7 +11,6 @@ import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.store
import timber.log.Timber
import kotlin.properties.Delegates
@ -19,12 +18,10 @@ import kotlin.properties.Delegates
/**
* Class that divide a new and legacy logic when user uses tokens list screen
*
* @property walletFeatureToggles wallet feature toggles
* @property getSelectedWalletSyncUseCase use case that returns selected wallet
* @property getCurrenciesUseCase use case that returns crypto currencies of a specified wallet
*/
internal class TokensListMigration(
private val walletFeatureToggles: WalletFeatureToggles,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
) {
@ -80,24 +77,11 @@ internal class TokensListMigration(
}
fun onSaveButtonClick(
currentTokensList: List<TokenWithBlockchain>,
currentBlockchainList: List<Blockchain>,
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
if (walletFeatureToggles.isRedesignedScreenEnabled) {
saveByNewWay(changedTokensList = changedTokensList, changedBlockchainList = changedBlockchainList)
} else {
saveByOldWay(currentTokensList, currentBlockchainList, changedTokensList, changedBlockchainList)
}
}
private fun saveByNewWay(
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
store.dispatch(
action = TokensAction.NewSaveChanges(
action = TokensAction.SaveChanges(
currentTokens = currentNewTokens,
currentCoins = currentNewCoins,
changedTokens = changedTokensList.mapNotNull {
@ -119,23 +103,4 @@ internal class TokensListMigration(
),
)
}
private fun saveByOldWay(
currentTokensList: List<TokenWithBlockchain>,
currentBlockchainList: List<Blockchain>,
changedTokensList: MutableList<TokenWithBlockchain>,
changedBlockchainList: List<Blockchain>,
) {
val scanResponse = store.state.globalState.scanResponse ?: return
store.dispatch(
action = TokensAction.LegacySaveChanges(
currentTokens = currentTokensList,
currentBlockchains = currentBlockchainList,
changedTokens = changedTokensList,
changedBlockchains = changedBlockchainList,
scanResponse = scanResponse,
),
)
}
}

View file

@ -22,7 +22,6 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.common.extensions.getNetworkName
import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor
@ -70,7 +69,6 @@ internal class TokensListViewModel @Inject constructor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
analyticsEventHandler: AnalyticsEventHandler,
getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
walletFeatureToggles: WalletFeatureToggles,
) : ViewModel(), DefaultLifecycleObserver {
private val isManageAccess = store.state.tokensState.isManageAccess
@ -88,7 +86,6 @@ internal class TokensListViewModel @Inject constructor(
private var changedBlockchainList: MutableList<Blockchain> = mutableListOf()
private val tokensListMigration = TokensListMigration(
walletFeatureToggles = walletFeatureToggles,
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
getCurrenciesUseCase = getCurrenciesUseCase,
)
@ -307,8 +304,6 @@ internal class TokensListViewModel @Inject constructor(
fun onSaveButtonClick() {
analyticsSender.sendWhenSaveButtonClicked()
tokensListMigration.onSaveButtonClick(
currentTokensList = currentTokensList,
currentBlockchainList = currentBlockchainList,
changedTokensList = changedTokensList,
changedBlockchainList = changedBlockchainList,
)

View file

@ -2,14 +2,11 @@ package com.tangem.tap.features.tokens.legacy.redux
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.flatMap
import com.tangem.core.navigation.NavigationAction
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.configs.CardConfig
@ -17,26 +14,24 @@ import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.supportsHdWallet
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletconnect.WalletConnectActions
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
@Suppress("LargeClass")
object TokensMiddleware {
@ -53,15 +48,14 @@ object TokensMiddleware {
{ next ->
{ action ->
when (action) {
is TokensAction.LegacySaveChanges -> handleLegacySaveChanges(action)
is TokensAction.NewSaveChanges -> handleNewSaveChanges(action)
is TokensAction.SaveChanges -> handleSaveChanges(action)
}
next(action)
}
}
}
private fun handleNewSaveChanges(action: TokensAction.NewSaveChanges) {
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
scope.launch {
val scanResponse = action.userWallet.scanResponse
@ -74,7 +68,7 @@ object TokensMiddleware {
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
val tokensToRemove = currentTokens.filterNot { token -> action.changedTokens.any { it == token } }
removeNewCurrenciesIfNeeded(
removeCurrenciesIfNeeded(
userWalletId = action.userWallet.walletId,
currencies = blockchainsToRemove + tokensToRemove,
)
@ -91,140 +85,14 @@ object TokensMiddleware {
if (scanResponse.supportsHdWallet()) {
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
submitNewAdd(
submitAdd(
userWallet = action.userWallet,
updatedScanResponse = it,
currencyList = currencyList,
)
}
} else {
submitNewAdd(
userWallet = action.userWallet,
updatedScanResponse = scanResponse,
currencyList = currencyList,
)
}
}
}
private fun handleLegacySaveChanges(action: TokensAction.LegacySaveChanges) {
scope.launch {
val scanResponse = action.scanResponse
val currentTokens = action.currentTokens
val currentBlockchains = action.currentBlockchains
val blockchainsToAdd = action.changedBlockchains.filterNot(currentBlockchains::contains)
val blockchainsToRemove = currentBlockchains.filterNot(action.changedBlockchains::contains)
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
val tokensToRemove =
currentTokens.filterNot { token -> action.changedTokens.any { it.token == token.token } }
removeLegacyCurrenciesIfNeeded(
currencies = convertToCurrencies(
blockchains = blockchainsToRemove,
tokens = tokensToRemove,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
),
)
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
store.dispatchDebugErrorNotification(message = "Nothing to save")
store.dispatchOnMain(NavigationAction.PopBackTo())
return@launch
}
val currencyList = convertToCurrencies(
blockchains = blockchainsToAdd,
tokens = tokensToAdd,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
)
if (scanResponse.supportsHdWallet()) {
deriveMissingBlockchains(scanResponse, currencyList) {
submitLegacyAdd(it, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
} else {
submitLegacyAdd(scanResponse, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
}
}
private fun convertToCurrencies(
blockchains: List<Blockchain>,
tokens: List<TokenWithBlockchain>,
derivationStyle: DerivationStyle?,
): List<Currency> {
return blockchains.map { Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) } +
tokens.map {
Currency.Token(
token = it.token,
blockchain = it.blockchain,
derivationPath = it.blockchain.derivationPath(derivationStyle)?.rawPath,
)
}
}
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
currencyList: List<Currency>,
onSuccess: (ScanResponse) -> Unit,
) {
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull { currency ->
val curve = config.primaryCurve(currency.blockchain)
curve?.let { getLegacyDerivations(curve, scanResponse, currency) }
}
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
derivationDataList.forEach {
val current = this[it.derivations.first]
if (current != null) {
current.addAll(it.derivations.second)
current.distinct()
} else {
this[it.derivations.first] = it.derivations.second.toMutableList()
}
}
}
if (derivations.isEmpty()) {
onSuccess(scanResponse)
return
}
scope.launch {
val result = tangemSdkManager.derivePublicKeys(
cardId = null,
derivations = derivations,
)
when (result) {
is CompletionResult.Success -> {
val newDerivedKeys = result.data.entries
val oldDerivedKeys = scanResponse.derivedKeys
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
ExtendedPublicKeysMap(oldDerivations + newDerivations)
}
val updatedScanResponse = scanResponse.copy(
derivedKeys = updatedDerivedKeys,
)
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
delay(DELAY_SDK_DIALOG_CLOSE)
onSuccess(updatedScanResponse)
}
is CompletionResult.Failure -> {
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
}
submitAdd(action.userWallet, scanResponse, currencyList)
}
}
}
@ -237,7 +105,7 @@ object TokensMiddleware {
val config = CardConfig.createConfig(scanResponse.card)
val derivationDataList = currencyList.mapNotNull { currency ->
val curve = config.primaryCurve(blockchain = Blockchain.fromId(currency.network.id.value))
curve?.let { getNewDerivations(curve, scanResponse, currency) }
curve?.let { getDerivations(curve, scanResponse, currency) }
}
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
derivationDataList.forEach {
@ -286,42 +154,7 @@ object TokensMiddleware {
}
}
private fun getLegacyDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
currency: Currency,
): DerivationData? {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
val supportedCurves = currency.blockchain.getSupportedCurves()
val path = currency.blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
.takeIf { supportedCurves.contains(curve) }
val customPath = currency.derivationPath?.let {
DerivationPath(it)
}.takeIf { supportedCurves.contains(curve) }
val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList()
if (bothCandidates.isEmpty()) return null
if (currency is Currency.Blockchain && currency.blockchain == Blockchain.Cardano) {
currency.derivationPath?.let {
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
}
}
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
val alreadyDerivedKeys: ExtendedPublicKeysMap =
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
if (toDerive.isEmpty()) return null
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
}
private fun getNewDerivations(
private fun getDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
currency: CryptoCurrency,
@ -359,28 +192,7 @@ object TokensMiddleware {
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
private fun submitLegacyAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add currencies, no user wallet selected")
return
}
scope.launch {
userWalletsListManager.update(
userWalletId = selectedUserWallet.walletId,
update = { userWallet ->
userWallet.copy(scanResponse = scanResponse)
},
)
.flatMap { updatedUserWallet ->
walletCurrenciesManager.addCurrencies(
userWallet = updatedUserWallet,
currenciesToAdd = currencyList,
)
}
}
}
private fun submitNewAdd(
private fun submitAdd(
userWallet: UserWallet,
updatedScanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,
@ -398,16 +210,7 @@ object TokensMiddleware {
store.dispatchOnMain(NavigationAction.PopBackTo())
}
private suspend fun removeLegacyCurrenciesIfNeeded(currencies: List<Currency>) {
if (currencies.isEmpty()) return
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to remove currencies, no user wallet selected")
return
}
walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies)
}
private suspend fun removeNewCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)

View file

@ -4,9 +4,6 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.doOnFailure
import com.tangem.common.extensions.guard
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
@ -18,12 +15,8 @@ import com.tangem.lib.crypto.models.Currency.NonNativeToken
import com.tangem.lib.crypto.models.ProxyAmount
import com.tangem.lib.crypto.models.ProxyFiatCurrency
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import com.tangem.tap.walletStoresManager
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
import java.math.BigDecimal
import com.tangem.tap.features.wallet.models.Currency as WalletCurrency
class UserWalletManagerImpl(
private val appStateHolder: AppStateHolder,
@ -104,39 +97,12 @@ class UserWalletManagerImpl(
}
override suspend fun addToken(currency: Currency, derivationPath: String?) {
val blockchain = requireNotNull(Blockchain.fromNetworkId(currency.networkId)) { "blockchain not found" }
val blockchainNetwork = BlockchainNetwork(blockchain, derivationPath, emptyList())
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add token, no user wallet selected")
return
}
walletCurrenciesManager.addCurrencies(
userWallet = selectedUserWallet,
currenciesToAdd = listOf(currency.toWalletCurrency(blockchainNetwork)),
)
// FIXME: Can be removed, used only in learn2earn
}
override suspend fun hideAllTokens() {
val userWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("No user wallets selected")
return
}
val currencies = walletStoresManager.get(userWallet.walletId)
.firstOrNull()
?.flatMap { walletStore ->
walletStore.walletsData.map { it.currency }
}
.guard {
Timber.d("No currencies found")
return
}
walletCurrenciesManager.removeCurrencies(userWallet, currenciesToRemove = currencies)
.doOnFailure { e ->
Timber.e(e, "Unable to delete all currencies")
}
// FIXME: Used only in Tester Actions
Timber.w("Not implemented")
}
override suspend fun getWalletAddress(networkId: String, derivationPath: String?): String {
@ -237,18 +203,4 @@ private fun NonNativeToken.toSdkToken(): Token {
contractAddress = this.contractAddress,
decimals = this.decimalCount,
)
}
private fun Currency.toWalletCurrency(network: BlockchainNetwork): WalletCurrency {
return when (this) {
is NativeToken -> WalletCurrency.Blockchain(
blockchain = network.blockchain,
derivationPath = network.derivationPath,
)
is NonNativeToken -> WalletCurrency.Token(
token = this.toSdkToken(),
blockchain = network.blockchain,
derivationPath = network.derivationPath,
)
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.domain.tokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import org.rekotlin.Action
@ -15,16 +14,7 @@ sealed interface TokensAction : Action {
object ReadAccess : SetArgs
}
@Deprecated("Action is used for saving data by old way. It will be removed after deleting of legacy wallet screen")
data class LegacySaveChanges(
val currentTokens: List<TokenWithBlockchain>,
val currentBlockchains: List<Blockchain>,
val changedTokens: List<TokenWithBlockchain>,
val changedBlockchains: List<Blockchain>,
val scanResponse: ScanResponse,
) : TokensAction
data class NewSaveChanges(
data class SaveChanges(
val currentTokens: List<CryptoCurrency.Token>,
val currentCoins: List<CryptoCurrency.Coin>,
val changedTokens: List<CryptoCurrency.Token>,