Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 11:58:05 +04:00
parent bf3a15c114
commit 475bd04c09
19 changed files with 0 additions and 1772 deletions

View file

@ -10,8 +10,6 @@ import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.Logger
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.filter.OneTimeEventFilter
@ -35,7 +33,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -62,11 +59,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.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
@ -93,32 +85,6 @@ lateinit var shopService: TangemShopService
internal lateinit var userTokensRepository: UserTokensRepository
internal lateinit var derivationsFinder: DerivationsFinder
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
private val walletManagersRepository by lazy {
WalletManagersRepository.provideDefaultImplementation(
walletManagerFactory = WalletManagerFactory(
config = store.state.globalState.configManager
?.config
?.blockchainSdkConfig
?: BlockchainSdkConfig(),
),
)
}
private val walletAmountsRepository by lazy {
WalletAmountsRepository.provideDefaultImplementation(
tangemTechService = store.state.domainNetworks.tangemTechService,
)
}
val walletStoresManager by lazy {
WalletStoresManager.provideDefaultImplementation(
userTokensRepository = userTokensRepository,
walletStoresRepository = walletStoresRepository,
walletManagersRepository = walletManagersRepository,
walletAmountsRepository = walletAmountsRepository,
appCurrencyProvider = { store.state.globalState.appCurrency },
)
}
@HiltAndroidApp
internal class TapApplication : Application(), ImageLoaderFactory {
@ -263,7 +229,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
)
appStateHolder.mainStore = store
appStateHolder.userTokensRepository = userTokensRepository
appStateHolder.walletStoresManager = walletStoresManager
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
}

View file

@ -1,39 +0,0 @@
package com.tangem.tap.domain.walletStores
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.core.TangemError
sealed class WalletStoresError(code: Int) : TangemError(code) {
override val silent: Boolean
get() = (cause as? TangemError)?.silent == true
override val messageResId: Int? = null
override val message: String?
get() = customMessage
class FetchFiatRatesError(
currencies: List<String>,
override val cause: Throwable?,
) : WalletStoresError(code = 60011) {
override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies"
}
class UnknownBlockchain : WalletStoresError(code = 60012) {
override var customMessage: String = "Unknown blockchain"
}
object NoInternetConnection : WalletStoresError(code = 60013) {
override var customMessage: String = "No internet connection"
}
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(code = 60014) {
override var customMessage: String = "Wallet manager can not be created for $blockchain"
}
class UpdateWalletManagerTokensError(
blockchain: Blockchain,
override val cause: Throwable,
) : WalletStoresError(code = 600015) {
override var customMessage: String = "Unable to update wallet manager tokens for currency $blockchain: $cause"
}
}

View file

@ -1,85 +0,0 @@
package com.tangem.tap.domain.walletStores
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
interface WalletStoresManager {
/**
* Get all [WalletStoreModel]s updates
*
* @return [Flow] with map of [WalletStoreModel] list assigned by [UserWalletId]
* */
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
/**
* Get [WalletStoreModel]s updates which associated with provided [UserWalletId]
*
* @param userWalletId [UserWalletId] of user wallet
*
* @return [Flow] with [WalletStoreModel] list
* */
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
/**
* Delete [WalletStoreModel]s associated with provided [UserWalletId]s
*
* @param userWalletsIds [UserWalletId] list
*
* @return [CompletionResult] of operation
* */
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
/**
* Clear all [WalletStoreModel]s
*
* @return [CompletionResult] of operation
* */
suspend fun clear(): CompletionResult<Unit>
/**
* Fetch wallet stores associated with provided [UserWallet]. Fetched [WalletStoreModel]s updates can be observed
* with [get] and [getAll] methods
*
* @param userWallet [UserWallet] to fetch [WalletStoreModel]s
*
* @return [CompletionResult] of operation
* */
suspend fun fetch(userWallet: UserWallet, refresh: Boolean = false): CompletionResult<Unit>
/**
* Fetch wallet stores associated with provided [UserWallet]s. Fetched [WalletStoreModel]s updates can be observed
* with [get] and [getAll] methods
*
* @param userWallets [UserWallet]s list to fetch [WalletStoreModel]s
*
* @return [CompletionResult] of operation
* */
suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean = false): CompletionResult<Unit>
/**
* Update [WalletStoreModel]s amounts associated with provided [UserWallet]s
*
* @param userWallets [UserWallet]s list to update [WalletStoreModel]s amounts
*
* @return [CompletionResult] of operation
* */
suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit>
suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit>
// For provider
companion object
}

View file

@ -1,30 +0,0 @@
package com.tangem.tap.domain.walletStores.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.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.implementation.DefaultWalletStoresManager
import com.tangem.tap.domain.walletStores.implementation.DummyWalletStoresManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
fun WalletStoresManager.Companion.provideDummyImplementation(): WalletStoresManager {
return DummyWalletStoresManager()
}
fun WalletStoresManager.Companion.provideDefaultImplementation(
userTokensRepository: UserTokensRepository,
walletStoresRepository: WalletStoresRepository,
walletAmountsRepository: WalletAmountsRepository,
walletManagersRepository: WalletManagersRepository,
appCurrencyProvider: () -> FiatCurrency,
): WalletStoresManager {
return DefaultWalletStoresManager(
userTokensRepository = userTokensRepository,
walletStoresRepository = walletStoresRepository,
walletAmountsRepository = walletAmountsRepository,
walletManagersRepository = walletManagersRepository,
appCurrencyProvider = appCurrencyProvider,
)
}

View file

@ -1,202 +0,0 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.*
import com.tangem.domain.common.util.derivationStyleProvider
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.WalletStoreModel
import com.tangem.tap.domain.model.builders.WalletStoreBuilder
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateSelectedAddress
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
internal class DefaultWalletStoresManager(
private val userTokensRepository: UserTokensRepository,
private val walletStoresRepository: WalletStoresRepository,
private val walletAmountsRepository: WalletAmountsRepository,
private val walletManagersRepository: WalletManagersRepository,
private val appCurrencyProvider: () -> FiatCurrency,
) : WalletStoresManager {
private val state = MutableStateFlow(State())
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
return walletStoresRepository.getAll()
}
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
return walletStoresRepository.get(userWalletId)
.distinctUntilChanged()
}
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
return walletStoresRepository.getSync(userWalletId)
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
return walletStoresRepository.delete(userWalletsIds)
.flatMap { walletManagersRepository.delete(userWalletsIds) }
}
override suspend fun clear(): CompletionResult<Unit> {
return walletStoresRepository.clear()
}
override suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
val fiatCurrency = appCurrencyProvider.invoke()
val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency
state.update { prevState ->
prevState.copy(
fiatCurrency = fiatCurrency,
)
}
userWallets
.mapNotNull { userWallet ->
val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId)
if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) {
fetchWalletsIfNeeded(userWallet)
} else {
null
}
}
.fold(arrayListOf<UserWallet>()) { acc, data ->
acc.apply { add(data) }
}
.flatMap {
walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency)
}
}
override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult<Unit> {
return fetch(listOf(userWallet), refresh)
}
override suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit> {
val fiatCurrency = appCurrencyProvider.invoke()
return walletAmountsRepository.updateAmountsForUserWallets(userWallets, fiatCurrency)
.doOnSuccess {
state.update { prevState ->
prevState.copy(
fiatCurrency = fiatCurrency,
)
}
}
}
override suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit> {
return walletStoresRepository.update(userWalletId) { walletStores ->
walletStores
.firstOrNull {
it.blockchain == currency.blockchain &&
it.derivationPath?.rawPath == currency.derivationPath
}
?.updateSelectedAddress(currency, addressType)
}
}
private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult<UserWallet> {
return if (userWallet.isMultiCurrency) {
fetchMultiWallets(userWallet)
} else {
fetchSingleWallet(userWallet)
}
.map { userWallet }
}
private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult<Unit> {
val scanResponse = userWallet.scanResponse
val derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle()
val userTokens = withContext(Dispatchers.IO) {
userTokensRepository.getUserTokens(scanResponse.card, derivationStyle)
}
val userWalletId = userWallet.walletId
return withContext(Dispatchers.Default) {
userTokens.toBlockchainNetworks()
.also { blockchainNetworks ->
walletStoresRepository.deleteDifference(
userWalletId = userWalletId,
currentBlockchains = blockchainNetworks.map {
Currency.Blockchain(it.blockchain, it.derivationPath)
},
)
}
.map { blockchainNetwork ->
val storeWalletStore: suspend (WalletManager?) -> CompletionResult<Unit> =
{ walletManager ->
walletStoresRepository.storeOrUpdate(
userWalletId = userWalletId,
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
.walletManager(walletManager)
.build(),
)
}
walletManagersRepository.findOrMakeMultiCurrencyWalletManager(
userWallet = userWallet,
blockchainNetwork = blockchainNetwork,
)
.flatMap { walletManager ->
storeWalletStore(walletManager)
}
.flatMapOnFailure { error ->
when (error) {
is WalletStoresError.WalletManagerNotCreated,
is WalletStoresError.UpdateWalletManagerTokensError,
-> storeWalletStore(null)
else -> CompletionResult.Failure(error)
}
}
}
.fold()
}
}
private suspend fun fetchSingleWallet(userWallet: UserWallet): CompletionResult<Unit> {
return walletManagersRepository.findOrMakeSingleCurrencyWalletManager(
userWallet = userWallet,
)
.flatMap { walletManager ->
val userWalletId = userWallet.walletId
walletStoresRepository.storeOrUpdate(
userWalletId = userWalletId,
walletStore = WalletStoreBuilder(userWallet, walletManager)
.build(),
)
}
.flatMapOnFailure { error ->
when (error) {
is WalletStoresError.WalletManagerNotCreated,
is WalletStoresError.UpdateWalletManagerTokensError,
-> CompletionResult.Success(Unit)
else -> CompletionResult.Failure(error)
}
}
}
internal data class State(
val fiatCurrency: FiatCurrency? = null,
)
}

View file

@ -1,53 +0,0 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
internal class DummyWalletStoresManager : WalletStoresManager {
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
return emptyFlow()
}
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
return emptyFlow()
}
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
return emptyList()
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun clear(): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.tap.domain.walletStores.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.model.WalletStoreModel
interface WalletAmountsRepository {
/**
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
* @param userWallets list of [UserWallet] which will be used to get the list of associated [WalletStoreModel]
* @param fiatCurrency current app [FiatCurrency]
* */
suspend fun updateAmountsForUserWallets(
userWallets: List<UserWallet>,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit>
/**
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
* @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel]
* @param fiatCurrency current app [FiatCurrency]
* */
suspend fun updateAmountsForUserWallet(userWallet: UserWallet, fiatCurrency: FiatCurrency): CompletionResult<Unit>
suspend fun updateAmountsForWalletStores(
walletStores: List<WalletStoreModel>,
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit>
/**
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
* @param walletStore [WalletStoreModel] to update
* @param userWallet [UserWallet] associated with provided [walletStore]
* @param fiatCurrency current app [FiatCurrency]
* */
suspend fun updateAmountsForWalletStore(
walletStore: WalletStoreModel,
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit>
companion object
}

View file

@ -1,42 +0,0 @@
package com.tangem.tap.domain.walletStores.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
interface WalletStoresRepository {
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
suspend fun contains(userWalletId: UserWalletId): Boolean
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun deleteDifference(
userWalletId: UserWalletId,
currentBlockchains: List<Currency.Blockchain>,
): CompletionResult<Unit>
suspend fun clear(): CompletionResult<Unit>
suspend fun storeOrUpdate(userWalletId: UserWalletId, walletStore: WalletStoreModel): CompletionResult<Unit>
/**
* Updates [WalletStoreModel] in storage for user wallet with provided [UserWalletId]
*
* @param userWalletId [UserWalletId] of user wallet
* @param operation Lambda which receives list of [WalletStoreModel] assigned to user wallet with [userWalletId]
* and returns updated [WalletStoreModel]. If null returned then do nothing
*
* @return [CompletionResult] of operation
* */
suspend fun update(
userWalletId: UserWalletId,
operation: (List<WalletStoreModel>) -> WalletStoreModel?,
): CompletionResult<Unit>
companion object
}

View file

@ -1,31 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.di
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletManagersRepository
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletStoresRepository
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
fun WalletStoresRepository.Companion.provideDefaultImplementation(): WalletStoresRepository {
return DefaultWalletStoresRepository()
}
fun WalletManagersRepository.Companion.provideDefaultImplementation(
walletManagerFactory: WalletManagerFactory,
): WalletManagersRepository {
return DefaultWalletManagersRepository(walletManagerFactory)
}
fun WalletAmountsRepository.Companion.provideDefaultImplementation(
tangemTechService: TangemTechService,
): WalletAmountsRepository {
// TODO("After adding DI") get dependencies by DI
return DefaultWalletAmountsRepository(
tangemTechApi = tangemTechService.api,
dispatchers = AppCoroutineDispatcherProvider(),
)
}

View file

@ -1,447 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.implementation
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.extensions.Result.Failure
import com.tangem.blockchain.extensions.Result.Success
import com.tangem.common.*
import com.tangem.common.core.TangemError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.implementation.utils.*
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.filterByCoin
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
import java.math.BigDecimal
import kotlin.time.Duration
@Suppress("LargeClass")
internal class DefaultWalletAmountsRepository(
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
) : WalletAmountsRepository {
override suspend fun updateAmountsForUserWallets(
userWallets: List<UserWallet>,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit> {
return if (userWallets.isEmpty()) {
CompletionResult.Success(Unit)
} else {
withContext(Dispatchers.Default) {
awaitAll(
async { fetchAmountsForUserWallets(userWallets) },
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
).fold()
}
}
}
override suspend fun updateAmountsForUserWallet(
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit> {
return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency)
}
override suspend fun updateAmountsForWalletStores(
walletStores: List<WalletStoreModel>,
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit> {
return if (walletStores.isEmpty()) {
CompletionResult.Success(Unit)
} else {
withContext(Dispatchers.Default) {
val userWalletId = userWallet.walletId
val scanResponse = userWallet.scanResponse
awaitAll(
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
).fold()
}
}
}
override suspend fun updateAmountsForWalletStore(
walletStore: WalletStoreModel,
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit> {
return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency)
}
private suspend fun fetchFiatRates(
userWallets: List<UserWallet>,
walletStores: List<WalletStoreModel>?,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit> {
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
if (!networkConnectionManager.isOnline) {
return CompletionResult.Failure(WalletStoresError.NoInternetConnection)
}
val walletStoresInternal = walletStores ?: getWalletStores(userWallets)
val currencies = walletStoresInternal.asSequence().flatMap { it.walletsData }.map { it.currency }
val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList()
return withContext(dispatchers.io) {
runCatching {
tangemTechApi.getRates(
fiatCurrency.code.lowercase(),
coinsIds.joinToString(","),
)
}.onSuccess {
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
return@withContext CompletionResult.Success(Unit)
}.onFailure {
val error = WalletStoresError.FetchFiatRatesError(
currencies = currencies.map(Currency::currencySymbol).toList(),
cause = it,
)
Timber.e(
error,
"""
Unable to fetch fiat rates
|- Coins ids: $coinsIds
""".trimIndent(),
)
return@withContext CompletionResult.Failure(error)
}
error("Unreachable code because runCatching must return result")
}
}
private suspend fun fetchAmountsForUserWallets(userWallets: List<UserWallet>): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
}
private suspend fun fetchAmountsForUserWallet(userWallet: UserWallet): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
val userWalletId = userWallet.walletId
val scanResponse = userWallet.scanResponse
val walletStores = getWalletStores(listOf(userWallet))
fetchAmountForWalletStores(userWalletId, scanResponse, walletStores)
}
private suspend fun fetchAmountForWalletStores(
userWalletId: UserWalletId,
scanResponse: ScanResponse,
walletStores: List<WalletStoreModel>,
): CompletionResult<Unit> = coroutineScope {
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
if (!networkConnectionManager.isOnline) {
walletStores.forEach {
updateWalletStoreWithUnreachable(it)
}
return@coroutineScope CompletionResult.Failure(WalletStoresError.NoInternetConnection)
}
walletStores.map { walletStore ->
async {
// TODO: Find wallet manager via [com.tangem.domain.wallets.legacy.WalletManagersRepository]
val walletManager = walletStore.walletManager
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
}
}.awaitAll().fold()
}
private suspend fun fetchAmountsForWalletStore(
userWalletId: UserWalletId,
scanResponse: ScanResponse,
walletStore: WalletStoreModel,
walletManager: WalletManager?,
): CompletionResult<Unit> {
val isDerivationMissed = with(walletStore) {
derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath)
}
return when {
isDerivationMissed -> {
updateWalletStoreWithMissedDerivation(walletStore)
}
walletManager == null -> {
updateWalletStoreWithUnreachable(walletStore)
}
else -> {
updateWalletManager(scanResponse, walletManager)
.map {
updateWalletManagerInStorage(userWalletId, walletManager)
}
.flatMap {
updateWalletStoreWithAmounts(
walletStore = walletStore,
updatedWallet = walletManager.wallet,
// FIXME: move DemoHelper to Demo core module maybe
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
)
}
.flatMap {
fetchWalletStoreRentIfNeeded(walletStore, walletManager)
}
.flatMapOnFailure { error ->
updateWalletStoreWithError(
walletStore = walletStore,
wallet = walletManager.wallet,
error = error,
)
}
}
}
}
private suspend fun updateWalletManager(
scanResponse: ScanResponse,
walletManager: WalletManager,
demoCardsDelay: Duration = with(Duration) { 500.milliseconds },
): CompletionResult<Unit> = catching {
if (scanResponse.isDemoCard() || TestActions.testAmountInjectionForWalletManagerEnabled) {
delay(demoCardsDelay)
TestActions.testAmountInjectionForWalletManagerEnabled = false
} else {
walletManager.update()
}
}
private suspend fun fetchWalletStoreRentIfNeeded(
walletStore: WalletStoreModel,
walletManager: WalletManager,
): CompletionResult<Unit> {
val rentProvider = walletManager as? RentProvider ?: return CompletionResult.Success(Unit)
when (val result = rentProvider.minimalBalanceForRentExemption()) {
is Success -> {
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
val outgoingTxs = walletManager.wallet.getPendingTransactions(
PendingTransactionType.Outgoing,
).filterByCoin()
val rentExempt = result.data
val setRent = if (outgoingTxs.isEmpty()) {
balance < rentExempt
} else {
val outgoingAmount = outgoingTxs.sumOf { it.amountValue ?: BigDecimal.ZERO }
val rest = balance.minus(outgoingAmount)
balance < rest
}
updateWalletStoreWithRent(
walletStore = walletStore,
rent = if (setRent) {
WalletStoreModel.WalletRent(
rent = rentProvider.rentAmount(),
exemptionAmount = rentExempt,
)
} else {
null
},
)
}
is Failure -> Unit
}
return CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithError(walletStore: WalletStoreModel, wallet: Wallet, error: TangemError) =
withContext(Dispatchers.Default) {
Timber.e(
error,
"""
Unable to fetch amounts
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
if (error is BlockchainSdkError) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithError(
wallet = wallet,
error = error,
)
},
)
}
CompletionResult.Success(Unit)
} else {
CompletionResult.Failure(error)
}
}
private suspend fun updateWalletStoreWithAmounts(
walletStore: WalletStoreModel,
updatedWallet: Wallet,
isDemo: Boolean,
) = withContext(Dispatchers.Default) {
Timber.d(
"""
Fetched amounts
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
if (isDemo) {
it.updateWithDemoAmounts(wallet = updatedWallet)
} else {
it.updateWithAmounts(wallet = updatedWallet)
}
},
)
}
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithMissedDerivation(walletStore: WalletStoreModel) =
withContext(Dispatchers.Default) {
Timber.e(
"""
Missed derivation
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithMissedDerivation()
},
)
}
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithUnreachable(walletStore: WalletStoreModel) =
withContext(Dispatchers.Default) {
Timber.e(
"""
Wallet manager is null
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithUnreachable()
},
)
}
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoresWithFiatRates(
walletStores: List<WalletStoreModel>,
fiatRates: Map<String, Double>,
) = withContext(Dispatchers.Default) {
Timber.d(
"""
Fetched fiat rates
|- User wallets ids: ${walletStores.map { it.userWalletId }.distinct()}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStores(
walletStoresToUpdate = walletStores,
update = {
it.updateWithFiatRates(rates = fiatRates)
},
)
}
}
private suspend fun updateWalletStoreWithRent(walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?) =
withContext(Dispatchers.Default) {
Timber.d(
"""
Fetched wallet rent
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|- Rent: $rent
""".trimIndent(),
)
if (rent != walletStore.walletRent) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithRent(rent)
},
)
}
}
}
private suspend fun updateWalletManagerInStorage(userWalletId: UserWalletId, walletManager: WalletManager) =
withContext(Dispatchers.Default) {
WalletManagerStorage.update { prevManagers ->
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
.addOrReplace(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain
}
prevManagers.apply {
set(userWalletId, newManagersForUserWallet)
}
}
}
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
return userWallets.map { it.walletId }.flatMap { userWalletId ->
WalletStoresStorage.getAll().firstOrNull()?.get(userWalletId).orEmpty()
}
}
}

View file

@ -1,223 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.implementation
import com.tangem.blockchain.common.*
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.extensions.makeWalletManagerForApp
import com.tangem.domain.common.util.cardTypesResolver
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
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
import timber.log.Timber
internal class DefaultWalletManagersRepository(
private val walletManagerFactory: WalletManagerFactory,
) : WalletManagersRepository {
private val walletManagersStorage = WalletManagerStorage
override suspend fun findOrMakeMultiCurrencyWalletManager(
userWallet: UserWallet,
blockchainNetwork: BlockchainNetwork,
): CompletionResult<WalletManager> {
return findOrMakeInternal(userWallet, blockchainNetwork)
}
override suspend fun findOrMakeSingleCurrencyWalletManager(
userWallet: UserWallet,
): CompletionResult<WalletManager> {
return findOrMakeInternal(userWallet, blockchainNetwork = null)
}
private suspend fun findOrMakeInternal(
userWallet: UserWallet,
blockchainNetwork: BlockchainNetwork?,
): CompletionResult<WalletManager> = withContext(Dispatchers.Default) {
val foundWalletManager = findWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchainNetwork?.blockchain,
derivationPath = blockchainNetwork?.derivationPath,
)
foundWalletManager?.updateTokens(
scanResponse = userWallet.scanResponse,
blockchainNetwork = blockchainNetwork,
)
?: makeAndStore(userWallet, blockchainNetwork)
}
private suspend fun makeAndStore(
userWallet: UserWallet,
blockchainNetwork: BlockchainNetwork?,
): CompletionResult<WalletManager> {
val scanResponse = userWallet.scanResponse
val blockchain = blockchainNetwork?.blockchain
?: scanResponse.cardTypesResolver.getBlockchain().let { blockchain ->
if (scanResponse.card.isTestCard) blockchain.getTestnetVersion() else blockchain
}
val derivationParams = getDerivationParams(
derivationPath = blockchainNetwork?.derivationPath,
derivationStyleProvider = scanResponse.derivationStyleProvider,
)
val walletManager = blockchain?.let {
walletManagerFactory.makeWalletManagerForApp(
scanResponse = userWallet.scanResponse,
blockchain = blockchain,
derivationParams = derivationParams,
)
}
return when {
blockchain == Blockchain.Unknown || blockchain == null -> {
val error = WalletStoresError.UnknownBlockchain()
Timber.e(
error,
"""
Unknown blockchain while creating wallet manager
|- User wallet ID: ${userWallet.walletId}
""".trimIndent(),
)
CompletionResult.Failure(error)
}
walletManager != null -> {
walletManager.updateTokens(
scanResponse = scanResponse,
blockchainNetwork = blockchainNetwork,
)
.doOnSuccess { store(userWallet.walletId, it) }
}
else -> {
val error = WalletStoresError.WalletManagerNotCreated(blockchain)
Timber.e(
error,
"""
Unable to create wallet manager
|- User wallet ID: ${userWallet.walletId}
|- Blockchain: $blockchain
|- Derivation path: ${blockchainNetwork?.derivationPath}
""".trimIndent(),
)
CompletionResult.Failure(error)
}
}
}
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> = catching {
walletManagersStorage.update { prevManagers ->
prevManagers.filterKeys { it !in userWalletIds } as HashMap<UserWalletId, List<WalletManager>>
}
}
override suspend fun delete(userWalletId: UserWalletId, blockchain: Blockchain): CompletionResult<Unit> = catching {
deleteInternal(userWalletId, blockchain)
}
private suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
walletManagersStorage.update { prevManagers ->
prevManagers.apply {
set(
key = userWalletId,
value = this[userWalletId].orEmpty() + walletManager,
)
}
}
}
private suspend fun deleteInternal(userWalletId: UserWalletId, blockchain: Blockchain?) {
walletManagersStorage.update { prevManagers ->
prevManagers.apply {
if (blockchain == null) {
set(
key = userWalletId,
value = emptyList(),
)
} else {
set(
key = userWalletId,
value = this[userWalletId]
?.filter { it.wallet.blockchain == blockchain }
.orEmpty(),
)
}
}
}
}
private fun WalletManager.updateTokens(
scanResponse: ScanResponse,
blockchainNetwork: BlockchainNetwork?,
): CompletionResult<WalletManager> {
val walletManager = this
return catching {
val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.cardTypesResolver.getPrimaryToken())
if (tokens != walletManager.cardTokens) {
// TODO: remove ability to manipulate with walletManager.cardTokens
walletManager.cardTokens.clear()
walletManager.wallet.removeAllTokens()
if (tokens.isNotEmpty()) {
walletManager.cardTokens.addAll(tokens)
// add empty amounts to prepare templates of tokens WalletDataModel
// see: WalletMangerWalletStoreBuilderImpl.build()
tokens.forEach { walletManager.wallet.setAmount(Amount(it)) }
}
}
walletManager
}
.mapFailure {
val error = WalletStoresError.UpdateWalletManagerTokensError(
blockchain = walletManager.wallet.blockchain,
cause = it,
)
Timber.e(error)
error
}
}
private suspend fun findWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain?,
derivationPath: String?,
): WalletManager? {
return walletManagersStorage.getAll()
.firstOrNull()
?.get(userWalletId)
?.let { userWalletManagers ->
if (blockchain == null) {
userWalletManagers.firstOrNull()
} else {
userWalletManagers.firstOrNull {
it.wallet.blockchain == blockchain &&
it.wallet.publicKey.derivationPath?.rawPath == derivationPath
}
}
}
}
private fun getDerivationParams(
derivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): DerivationParams? {
val derivationStyle = derivationStyleProvider.getDerivationStyle() ?: return null
return if (derivationPath == null) {
DerivationParams.Default(derivationStyle)
} else {
DerivationParams.Custom(DerivationPath(derivationPath))
}
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.implementation
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.implementation.utils.isSameWalletStore
import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithSelf
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
internal class DefaultWalletStoresRepository : WalletStoresRepository {
private val walletStoresStorage = WalletStoresStorage
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
return walletStoresStorage.getAll()
}
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
return getAll().map { it[userWalletId].orEmpty() }
}
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
return get(userWalletId).firstOrNull() ?: emptyList()
}
override suspend fun contains(userWalletId: UserWalletId): Boolean {
return getSync(userWalletId).isNotEmpty()
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> = catching {
walletStoresStorage.update { prevStores ->
prevStores.filterKeys { it !in userWalletsIds } as HashMap<UserWalletId, List<WalletStoreModel>>
}
}
override suspend fun deleteDifference(
userWalletId: UserWalletId,
currentBlockchains: List<Currency.Blockchain>,
): CompletionResult<Unit> = catching {
if (currentBlockchains != getSync(userWalletId)) {
walletStoresStorage.update { prevStores ->
prevStores.apply {
this[userWalletId] = this[userWalletId]
?.filter { it.blockchainWalletData.currency in currentBlockchains }
.orEmpty()
}
}
}
}
override suspend fun clear(): CompletionResult<Unit> = catching {
walletStoresStorage.update { hashMapOf() }
}
override suspend fun storeOrUpdate(
userWalletId: UserWalletId,
walletStore: WalletStoreModel,
): CompletionResult<Unit> = catching {
walletStoresStorage.update { prevStores ->
prevStores.addOrUpdate(userWalletId, walletStore)
}
}
override suspend fun update(
userWalletId: UserWalletId,
operation: (List<WalletStoreModel>) -> WalletStoreModel?,
): CompletionResult<Unit> = catching {
val walletStores = getSync(userWalletId).toMutableList()
val updatedWalletStore = operation(walletStores) ?: return CompletionResult.Success(Unit)
val index = walletStores.indexOfFirst { it.isSameWalletStore(updatedWalletStore) }
if (index == -1 || updatedWalletStore == walletStores[index]) return CompletionResult.Success(Unit)
walletStores[index] = updatedWalletStore
walletStoresStorage.update { prevStores ->
prevStores.apply {
this[userWalletId] = walletStores
}
}
}
private suspend fun HashMap<UserWalletId, List<WalletStoreModel>>.addOrUpdate(
userWalletId: UserWalletId,
walletStore: WalletStoreModel,
): HashMap<UserWalletId, List<WalletStoreModel>> = withContext(Dispatchers.Default) {
val currentWalletStores = this@addOrUpdate
val userWalletStores = currentWalletStores[userWalletId]
if (userWalletStores.isNullOrEmpty()) {
currentWalletStores.apply {
set(userWalletId, listOf(walletStore))
}
} else {
val currentWalletStore = userWalletStores.find(walletStore::isSameWalletStore)
if (currentWalletStore == null) {
currentWalletStores.apply {
set(userWalletId, userWalletStores + walletStore)
}
} else {
currentWalletStores.replaceWalletStore(
walletStoreToUpdate = currentWalletStore,
update = { it.updateWithSelf(walletStore) },
)
}
}
}
}

View file

@ -1,211 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.implementation.utils
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.core.TangemError
import com.tangem.domain.common.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.getPendingTransactions
import java.math.BigDecimal
internal fun WalletDataModel.updateWithFiatRate(fiatRate: BigDecimal?): WalletDataModel {
return this.copy(
fiatRate = fiatRate,
)
}
internal fun List<WalletDataModel>.updateWithFiatRates(fiatRates: Map<String, Double>): List<WalletDataModel> {
return this.map { walletData ->
val rate = fiatRates[walletData.currency.coinId]?.toBigDecimal()
walletData.updateWithFiatRate(rate)
}
}
internal fun List<WalletDataModel>.updateWithAmounts(wallet: Wallet): List<WalletDataModel> {
return this.map { walletData ->
walletData.updateWithAmount(wallet)
}
}
internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel {
val pendingTransactions = wallet.getPendingTransactions()
return this.copy(
status = when (currency) {
is Currency.Blockchain -> {
val amount = wallet.fundsAvailable(AmountType.Coin)
if (pendingTransactions.isEmpty()) {
WalletDataModel.VerifiedOnline(amount)
} else {
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
}
}
is Currency.Token -> {
val token = currency.token
val amount = wallet.fundsAvailable(AmountType.Token(token))
val hasTokenPendingTransactions = pendingTransactions.any {
it.transactionData.amount.currencySymbol == token.symbol
}
when {
hasTokenPendingTransactions -> {
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
}
pendingTransactions.isNotEmpty() -> {
// FIXME: Necessary to avoid passing pending transactions
// because SameCurrencyTransactionInProgress didn't use it. It used only to define main button
// availability. UI layer turned off pending transaction visibility for this state.
WalletDataModel.SameCurrencyTransactionInProgress(amount, pendingTransactions)
}
else -> {
WalletDataModel.VerifiedOnline(amount)
}
}
}
},
)
}
internal fun WalletDataModel.updateWithDemoAmount(wallet: Wallet): WalletDataModel {
val amount = DemoHelper.config.getBalance(wallet.blockchain)
wallet.setAmount(amount)
return this.copy(
status = WalletDataModel.VerifiedOnline(amount = amount.value ?: BigDecimal.ZERO),
)
}
internal fun List<WalletDataModel>.updateWithDemoAmounts(wallet: Wallet): List<WalletDataModel> {
return this.map { walletData ->
walletData.updateWithDemoAmount(wallet)
}
}
internal fun WalletDataModel.updateWithError(wallet: Wallet, error: TangemError): WalletDataModel {
return this.copy(
status = when (error) {
is BlockchainSdkError.AccountNotFound -> {
val amountToCreateAccount = wallet.blockchain
.amountToCreateAccount(wallet.getFirstToken())
if (amountToCreateAccount != null) {
WalletDataModel.NoAccount(
amountToCreateAccount = amountToCreateAccount,
)
} else {
WalletDataModel.Unreachable(
errorMessage = error.customMessage,
)
}
}
else -> WalletDataModel.Unreachable(
errorMessage = error.customMessage,
)
},
)
}
internal fun List<WalletDataModel>.updateWithError(wallet: Wallet, error: TangemError): List<WalletDataModel> {
return this.map { walletData ->
walletData.updateWithError(wallet, error)
}
}
internal fun WalletDataModel.updateWithSelf(newWalletData: WalletDataModel): WalletDataModel {
val oldWalletData = this
val oldStatus = oldWalletData.status
return oldWalletData.copy(
status = when (val newStatus = newWalletData.status) {
is WalletDataModel.Loading -> when (oldStatus) {
is WalletDataModel.MissedDerivation -> WalletDataModel.Loading
else -> oldStatus
}
is WalletDataModel.MissedDerivation,
is WalletDataModel.NoAccount,
is WalletDataModel.Unreachable,
is WalletDataModel.SameCurrencyTransactionInProgress,
is WalletDataModel.TransactionInProgress,
is WalletDataModel.VerifiedOnline,
-> newStatus
},
existentialDeposit = newWalletData.existentialDeposit,
walletAddresses = newWalletData.walletAddresses?.copy(
selectedAddress = oldWalletData.walletAddresses?.selectedAddress
?: newWalletData.walletAddresses.selectedAddress,
),
fiatRate = newWalletData.fiatRate ?: oldWalletData.fiatRate,
)
}
internal fun List<WalletDataModel>.updateWithMissedDerivation(): List<WalletDataModel> {
return this.map { walletData ->
walletData.copy(
status = WalletDataModel.MissedDerivation,
)
}
}
internal fun List<WalletDataModel>.updateWithUnreachable(): List<WalletDataModel> {
return this.map { walletData ->
walletData.copy(
status = WalletDataModel.Unreachable(
errorMessage = null,
amount = walletData.status.amount,
),
)
}
}
internal fun List<WalletDataModel>.updateWithSelf(newWalletsData: List<WalletDataModel>): List<WalletDataModel> {
val oldWalletsData = this
val updatedWalletsData = arrayListOf<WalletDataModel>()
newWalletsData.forEach { newWalletData ->
val walletDataToUpdate = oldWalletsData.firstOrNull(newWalletData::isSameWalletData)
if (walletDataToUpdate != null) {
updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData))
} else {
updatedWalletsData.add(newWalletData)
}
}
return updatedWalletsData
}
internal fun List<WalletDataModel>.updateSelectedAddress(
currency: Currency,
addressType: AddressType,
): List<WalletDataModel> {
val index = this.indexOfFirst { it.currency == currency }
if (index == -1) return this
val oldWalletData = this[index]
val updatedWalletData = oldWalletData.updateSelectedAddress(addressType)
if (oldWalletData == updatedWalletData) return this
return this.toMutableList().apply {
this[index] = updatedWalletData
}
}
internal fun WalletDataModel.updateSelectedAddress(addressType: AddressType): WalletDataModel {
val addresses = walletAddresses ?: return this
val selectedAddress = addresses.list
.firstOrNull { it.type == addressType }
?: addresses.selectedAddress
return this.copy(
walletAddresses = addresses.copy(
selectedAddress = selectedAddress,
),
)
}
internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean {
return this.currency == other.currency
}

View file

@ -1,131 +0,0 @@
package com.tangem.tap.domain.walletStores.repository.implementation.utils
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.core.TangemError
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import timber.log.Timber
internal inline fun HashMap<UserWalletId, List<WalletStoreModel>>.replaceWalletStore(
walletStoreToUpdate: WalletStoreModel,
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
): HashMap<UserWalletId, List<WalletStoreModel>> {
return replaceWalletStores(listOf(walletStoreToUpdate), update)
}
internal inline fun HashMap<UserWalletId, List<WalletStoreModel>>.replaceWalletStores(
walletStoresToUpdate: List<WalletStoreModel>,
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
): HashMap<UserWalletId, List<WalletStoreModel>> {
return this.apply {
val currentWalletStores = this
walletStoresToUpdate
.groupBy { it.userWalletId }
.forEach { (userWalletId, walletStoresToUpdate) ->
currentWalletStores[userWalletId] = currentWalletStores[userWalletId]
?.replaceWalletStores(walletStoresToUpdate, update)
.orEmpty()
}
}
}
internal fun WalletStoreModel.updateWithError(wallet: Wallet, error: TangemError): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithError(
wallet = wallet,
error = error,
),
)
}
internal fun WalletStoreModel.updateWithAmounts(wallet: Wallet): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithAmounts(wallet = wallet),
)
}
internal fun WalletStoreModel.updateWithDemoAmounts(wallet: Wallet): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithDemoAmounts(wallet = wallet),
)
}
internal fun WalletStoreModel.updateWithFiatRates(rates: Map<String, Double>): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithFiatRates(rates),
)
}
internal fun WalletStoreModel.updateWithSelf(newWalletStore: WalletStoreModel): WalletStoreModel {
val oldStore = this
return oldStore.copy(
derivationPath = newWalletStore.derivationPath,
walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData),
walletRent = newWalletStore.walletRent,
blockchainNetwork = newWalletStore.blockchainNetwork,
walletManager = newWalletStore.walletManager,
)
}
internal fun WalletStoreModel.updateWithMissedDerivation(): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithMissedDerivation(),
)
}
internal fun WalletStoreModel.updateWithUnreachable(): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateWithUnreachable(),
)
}
internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?): WalletStoreModel {
return this.copy(
walletRent = rent,
)
}
internal fun WalletStoreModel.updateSelectedAddress(currency: Currency, addressType: AddressType): WalletStoreModel {
return this.copy(
walletsData = walletsData.updateSelectedAddress(currency, addressType),
)
}
private inline fun List<WalletStoreModel>.replaceWalletStores(
walletStoresToUpdate: List<WalletStoreModel>,
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
): List<WalletStoreModel> {
val mutableStores = ArrayList<WalletStoreModel>(this)
walletStoresToUpdate.forEach { walletStoreToUpdate ->
val index = mutableStores.indexOfFirst(walletStoreToUpdate::isSameWalletStore)
// Can be possible if user hides wallet store when it's tokens is loading
if (index == -1) return@forEach
val currentWalletStore = mutableStores[index]
val updatedWalletStore = update(currentWalletStore)
if (currentWalletStore != updatedWalletStore) {
Timber.d(
"""
Update wallet store in storage
|- User wallet ID: ${updatedWalletStore.userWalletId}
|- Blockchain: ${updatedWalletStore.blockchain}
|- Derivation path: ${updatedWalletStore.derivationPath?.rawPath}
""".trimIndent(),
)
mutableStores[index] = updatedWalletStore
}
}
return mutableStores
}
internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean {
return this.userWalletId == other.userWalletId &&
this.blockchain == other.blockchain &&
this.derivationPath == other.derivationPath
}

View file

@ -1,37 +0,0 @@
package com.tangem.tap.domain.walletStores.storage
import com.tangem.blockchain.common.WalletManager
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
internal object WalletManagerStorage {
private val managers = MutableSharedFlow<HashMap<UserWalletId, List<WalletManager>>>(replay = 1)
private val mutex = Mutex()
init {
managers.tryEmit(hashMapOf())
}
fun getAll(): SharedFlow<Map<UserWalletId, List<WalletManager>>> {
return managers.asSharedFlow()
}
suspend fun update(
f: suspend (HashMap<UserWalletId, List<WalletManager>>) -> HashMap<UserWalletId, List<WalletManager>>,
) {
while (mutex.isLocked) {
delay(timeMillis = 60)
}
mutex.withLock {
val prevState = managers.first()
managers.emit(f(prevState))
}
}
}

View file

@ -1,37 +0,0 @@
package com.tangem.tap.domain.walletStores.storage
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
internal object WalletStoresStorage {
private val stores = MutableSharedFlow<HashMap<UserWalletId, List<WalletStoreModel>>>(replay = 1)
private val mutex = Mutex()
init {
stores.tryEmit(hashMapOf())
}
fun getAll(): SharedFlow<Map<UserWalletId, List<WalletStoreModel>>> {
return stores.asSharedFlow()
}
suspend fun update(
f: suspend (HashMap<UserWalletId, List<WalletStoreModel>>) -> HashMap<UserWalletId, List<WalletStoreModel>>,
) {
while (mutex.isLocked) {
delay(timeMillis = 60)
}
mutex.withLock {
val prevState = stores.first()
stores.emit(f(prevState))
}
}
}

View file

@ -386,7 +386,6 @@ class DetailsMiddleware {
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
return userWalletsListManager.clear()
.flatMap { walletStoresManager.clear() }
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()

View file

@ -158,7 +158,6 @@ internal class WelcomeMiddleware {
private suspend fun disableUserWalletsSaving() {
userWalletsListManager.clear()
.flatMap { walletStoresManager.clear() }
.flatMap { tangemSdkManager.clearSavedUserCodes() }
.doOnFailure { e ->
Timber.e(e, "Unable to clear user wallets")

View file

@ -15,7 +15,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@ -45,7 +44,6 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavControl
var userTokensRepository: UserTokensRepository? = null
var mainStore: Store<AppState>? = null
var tangemSdkManager: TangemSdkManager? = null
var walletStoresManager: WalletStoresManager? = null
var appFiatCurrency: FiatCurrency = FiatCurrency.Default
var exchangeService: ExchangeService? = null