Updated on 2026-08-14
This commit is contained in:
parent
f55f6f8461
commit
0fdcdda8d5
15 changed files with 213 additions and 250 deletions
|
|
@ -4,6 +4,9 @@ import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
import com.tangem.domain.managetokens.*
|
import com.tangem.domain.managetokens.*
|
||||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -57,11 +60,17 @@ internal object ManageTokensDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSaveManagedTokensUseCase(
|
fun provideSaveManagedTokensUseCase(
|
||||||
manageTokensRepository: ManageTokensRepository,
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
networksRepository: NetworksRepository,
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
): SaveManagedTokensUseCase {
|
): SaveManagedTokensUseCase {
|
||||||
return SaveManagedTokensUseCase(
|
return SaveManagedTokensUseCase(
|
||||||
manageTokensRepository = manageTokensRepository,
|
customTokensRepository = customTokensRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
networksRepository = networksRepository,
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -81,4 +90,10 @@ internal object ManageTokensDomainModule {
|
||||||
): ValidateDerivationPathUseCase {
|
): ValidateDerivationPathUseCase {
|
||||||
return ValidateDerivationPathUseCase(customTokensRepository)
|
return ValidateDerivationPathUseCase(customTokensRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCheckHasLinkedTokensUseCase(repository: ManageTokensRepository): CheckHasLinkedTokensUseCase {
|
||||||
|
return CheckHasLinkedTokensUseCase(repository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,12 +431,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideCheckHasLinkedTokensUseCase(currenciesRepository: CurrenciesRepository): CheckHasLinkedTokensUseCase {
|
|
||||||
return CheckHasLinkedTokensUseCase(currenciesRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,21 @@ internal class DefaultCustomTokensRepository(
|
||||||
return cryptoCurrencyFactory.createCoin(network)
|
return cryptoCurrencyFactory.createCoin(network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createToken(
|
||||||
|
managedCryptoCurrency: ManagedCryptoCurrency.Token,
|
||||||
|
sourceNetwork: ManagedCryptoCurrency.SourceNetwork.Default,
|
||||||
|
rawId: String?,
|
||||||
|
): CryptoCurrency.Token {
|
||||||
|
return cryptoCurrencyFactory.createToken(
|
||||||
|
network = sourceNetwork.network,
|
||||||
|
rawId = rawId,
|
||||||
|
name = managedCryptoCurrency.name,
|
||||||
|
symbol = managedCryptoCurrency.symbol,
|
||||||
|
decimals = sourceNetwork.decimals,
|
||||||
|
contractAddress = sourceNetwork.contractAddress,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun createCustomToken(
|
override suspend fun createCustomToken(
|
||||||
networkId: Network.ID,
|
networkId: Network.ID,
|
||||||
derivationPath: Network.DerivationPath,
|
derivationPath: Network.DerivationPath,
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,16 @@ package com.tangem.data.managetokens
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||||
import com.tangem.data.common.api.safeApiCall
|
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
|
||||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
|
||||||
import com.tangem.data.common.currency.getBlockchain
|
import com.tangem.data.common.currency.getBlockchain
|
||||||
import com.tangem.data.common.currency.hasCoinForToken
|
|
||||||
import com.tangem.data.common.utils.retryOnError
|
import com.tangem.data.common.utils.retryOnError
|
||||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||||
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
|
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
|
||||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
|
||||||
import com.tangem.datasource.api.express.models.TangemExpressValues
|
|
||||||
import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
|
||||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
|
||||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
|
|
@ -35,8 +24,6 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
|
||||||
import com.tangem.domain.walletmanager.model.TokenInfo
|
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.pagination.BatchFetchResult
|
import com.tangem.pagination.BatchFetchResult
|
||||||
|
|
@ -44,8 +31,6 @@ import com.tangem.pagination.BatchListSource
|
||||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
||||||
import com.tangem.pagination.toBatchFlow
|
import com.tangem.pagination.toBatchFlow
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
internal class DefaultManageTokensRepository(
|
internal class DefaultManageTokensRepository(
|
||||||
|
|
@ -54,15 +39,9 @@ internal class DefaultManageTokensRepository(
|
||||||
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val walletManagerFacade: WalletManagersFacade,
|
|
||||||
private val tangemExpressApi: TangemExpressApi,
|
|
||||||
private val expressAssetsStore: ExpressAssetsStore,
|
|
||||||
) : ManageTokensRepository {
|
) : ManageTokensRepository {
|
||||||
|
|
||||||
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory()
|
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory()
|
||||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
|
||||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
|
||||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
|
||||||
|
|
||||||
// region getTokenListBatchFlow
|
// region getTokenListBatchFlow
|
||||||
override fun getTokenListBatchFlow(
|
override fun getTokenListBatchFlow(
|
||||||
|
|
@ -108,7 +87,7 @@ internal class DefaultManageTokensRepository(
|
||||||
retryOnError(call = call)
|
retryOnError(call = call)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tokensResponse = getStoredUserTokens(request.params.userWalletId)
|
val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) }
|
||||||
val items = if (isFirstBatchFetching &&
|
val items = if (isFirstBatchFetching &&
|
||||||
tokensResponse != null &&
|
tokensResponse != null &&
|
||||||
userWallet != null &&
|
userWallet != null &&
|
||||||
|
|
@ -135,16 +114,6 @@ internal class DefaultManageTokensRepository(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun getStoredUserTokens(userWalletId: UserWalletId?): UserTokensResponse? {
|
|
||||||
return if (userWalletId != null) {
|
|
||||||
appPreferencesStore.getObjectSyncOrNull(
|
|
||||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||||
|
|
@ -160,158 +129,44 @@ internal class DefaultManageTokensRepository(
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region addManagedCurrencies
|
override suspend fun hasLinkedTokens(
|
||||||
override suspend fun saveManagedCurrencies(
|
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
currenciesToAdd: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
network: Network,
|
||||||
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
tempAddedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
) {
|
tempRemovedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
return withContext(dispatchers.io) {
|
): Boolean {
|
||||||
val savedCurrencies = requireNotNull(
|
val addedTokens = tempAddedTokens.mapToResponseTokens()
|
||||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
val removedTokens = tempRemovedTokens.mapToResponseTokens()
|
||||||
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
|
|
||||||
)
|
|
||||||
|
|
||||||
val filteredTokens = currenciesToAdd.mapToTokenDataList()
|
val storedTokens = requireNotNull(
|
||||||
.filterNot { tokenData ->
|
value = getSavedUserTokensResponseSync(userWalletId),
|
||||||
val blockchain = getBlockchain(networkId = tokenData.sourceNetwork.network.id)
|
lazyMessage = { "Unable to find tokens response for user wallet with provided ID: $userWalletId" },
|
||||||
val networkId = blockchain.toNetworkId()
|
|
||||||
val contractAddress = (tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress
|
|
||||||
savedCurrencies.tokens.firstOrNull { token ->
|
|
||||||
token.contractAddress == contractAddress &&
|
|
||||||
token.networkId == networkId &&
|
|
||||||
token.derivationPath == tokenData.sourceNetwork.network.derivationPath.value
|
|
||||||
} != null
|
|
||||||
}
|
|
||||||
val newCoins = createCoinsForNewTokens(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
newTokensNetworks = filteredTokens.mapNotNull { tokenData ->
|
|
||||||
(tokenData.sourceNetwork as? SourceNetwork.Default)?.network
|
|
||||||
},
|
|
||||||
savedCurrencies = savedCurrencies.tokens,
|
|
||||||
)
|
|
||||||
val tokensToSave = filteredTokens.mapToUserResponseToken()
|
|
||||||
|
|
||||||
val tokensToRemove = currenciesToRemove.mapToTokenDataList()
|
|
||||||
removeCurrenciesFromWalletManager(userWalletId = userWalletId, tokensDataList = tokensToRemove)
|
|
||||||
|
|
||||||
val newCurrencies = (newCoins + tokensToSave).distinct()
|
|
||||||
val updatedResponse = savedCurrencies.copy(
|
|
||||||
tokens = savedCurrencies.tokens + newCurrencies - tokensToRemove.mapToUserResponseToken().toSet(),
|
|
||||||
)
|
|
||||||
storeAndPushTokens(userWalletId = userWalletId, response = updatedResponse)
|
|
||||||
fetchExchangeableUserMarketCoinsByIds(userWalletId = userWalletId, userTokens = updatedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
|
||||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
|
||||||
appPreferencesStore.storeObject(
|
|
||||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
|
||||||
value = compatibleUserTokensResponse,
|
|
||||||
)
|
)
|
||||||
|
val newTokensList = storedTokens.tokens + addedTokens - removedTokens.toSet()
|
||||||
|
|
||||||
pushTokens(userWalletId, response)
|
return newTokensList.any {
|
||||||
}
|
it.contractAddress != null &&
|
||||||
|
it.networkId == network.backendId &&
|
||||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
it.derivationPath == network.derivationPath.value
|
||||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
|
||||||
Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchExchangeableUserMarketCoinsByIds(
|
private fun Map<ManagedCryptoCurrency.Token, Set<Network>>.mapToResponseTokens(): List<UserTokensResponse.Token> {
|
||||||
userWalletId: UserWalletId,
|
|
||||||
userTokens: UserTokensResponse,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
val tokensList = userTokens.tokens
|
|
||||||
.map {
|
|
||||||
LeastTokenInfo(
|
|
||||||
contractAddress = it.contractAddress ?: TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE,
|
|
||||||
network = it.networkId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokensList.isNotEmpty()) {
|
|
||||||
val response = tangemExpressApi.getAssets(
|
|
||||||
AssetsRequestBody(
|
|
||||||
tokensList = tokensList,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
expressAssetsStore.store(userWalletId, response.getOrThrow())
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun removeCurrenciesFromWalletManager(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
tokensDataList: List<TokenWithSourceNetwork>,
|
|
||||||
) {
|
|
||||||
walletManagerFacade.removeTokensByTokenInfo(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
tokenInfos = tokensDataList.mapNotNullTo(hashSetOf()) { tokenData ->
|
|
||||||
val contractAddress =
|
|
||||||
(tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress ?: return@mapNotNullTo null
|
|
||||||
TokenInfo(
|
|
||||||
network = tokenData.sourceNetwork.network,
|
|
||||||
name = tokenData.token.name,
|
|
||||||
symbol = tokenData.token.symbol,
|
|
||||||
contractAddress = contractAddress,
|
|
||||||
decimals = tokenData.sourceNetwork.decimals,
|
|
||||||
id = tokenData.token.id.value,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
walletManagerFacade.remove(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
networks = tokensDataList.mapNotNullTo(hashSetOf()) { tokenData ->
|
|
||||||
(tokenData.sourceNetwork as? SourceNetwork.Main)?.network
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun createCoinsForNewTokens(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
newTokensNetworks: List<Network>,
|
|
||||||
savedCurrencies: List<UserTokensResponse.Token>,
|
|
||||||
): List<UserTokensResponse.Token> {
|
|
||||||
return newTokensNetworks
|
|
||||||
.filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins
|
|
||||||
.mapNotNull {
|
|
||||||
cryptoCurrencyFactory.createCoin(
|
|
||||||
blockchain = getBlockchain(networkId = it.id),
|
|
||||||
extraDerivationPath = it.derivationPath.value,
|
|
||||||
derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.distinct()
|
|
||||||
.map(userTokensResponseFactory::createResponseToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<TokenWithSourceNetwork>.mapToUserResponseToken(): List<UserTokensResponse.Token> {
|
|
||||||
return map { tokenData ->
|
|
||||||
val blockchain = getBlockchain(tokenData.sourceNetwork.network.id)
|
|
||||||
UserTokensResponse.Token(
|
|
||||||
id = tokenData.token.id.value,
|
|
||||||
networkId = blockchain.toNetworkId(),
|
|
||||||
derivationPath = tokenData.sourceNetwork.network.derivationPath.value,
|
|
||||||
name = tokenData.token.name,
|
|
||||||
symbol = tokenData.token.symbol,
|
|
||||||
decimals = tokenData.sourceNetwork.decimals,
|
|
||||||
contractAddress = (tokenData.sourceNetwork as? SourceNetwork.Default)?.contractAddress,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Map<ManagedCryptoCurrency.Token, Set<Network>>.mapToTokenDataList(): List<TokenWithSourceNetwork> {
|
|
||||||
return flatMap { (token, networks) ->
|
return flatMap { (token, networks) ->
|
||||||
token.availableNetworks
|
token.availableNetworks
|
||||||
.filter { sourceNetwork -> networks.contains(sourceNetwork.network) }
|
.filter { sourceNetwork -> networks.contains(sourceNetwork.network) }
|
||||||
.map { sourceNetwork -> TokenWithSourceNetwork(token = token, sourceNetwork = sourceNetwork) }
|
.map { sourceNetwork ->
|
||||||
|
val blockchain = getBlockchain(sourceNetwork.network.id)
|
||||||
|
UserTokensResponse.Token(
|
||||||
|
id = token.id.value,
|
||||||
|
networkId = blockchain.toNetworkId(),
|
||||||
|
derivationPath = sourceNetwork.network.derivationPath.value,
|
||||||
|
name = token.name,
|
||||||
|
symbol = token.symbol,
|
||||||
|
decimals = sourceNetwork.decimals,
|
||||||
|
contractAddress = (sourceNetwork as? SourceNetwork.Default)?.contractAddress,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@ package com.tangem.data.managetokens.di
|
||||||
import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
||||||
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
||||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
|
|
@ -30,9 +28,6 @@ internal object ManageTokensDataModule {
|
||||||
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
|
||||||
tangemExpressApi: TangemExpressApi,
|
|
||||||
expressAssetsStore: ExpressAssetsStore,
|
|
||||||
): ManageTokensRepository {
|
): ManageTokensRepository {
|
||||||
return DefaultManageTokensRepository(
|
return DefaultManageTokensRepository(
|
||||||
tangemTechApi,
|
tangemTechApi,
|
||||||
|
|
@ -40,9 +35,6 @@ internal object ManageTokensDataModule {
|
||||||
manageTokensUpdateFetcher,
|
manageTokensUpdateFetcher,
|
||||||
appPreferencesStore,
|
appPreferencesStore,
|
||||||
dispatchers,
|
dispatchers,
|
||||||
walletManagersFacade,
|
|
||||||
tangemExpressApi,
|
|
||||||
expressAssetsStore,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -478,26 +478,6 @@ internal class DefaultCurrenciesRepository(
|
||||||
) ?: error("Unable to create token")
|
) ?: error("Unable to create token")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hasTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
|
||||||
val userWallet = getUserWallet(userWalletId)
|
|
||||||
fetchTokensIfCacheExpired(userWallet, refresh = false)
|
|
||||||
|
|
||||||
val storedTokens = requireNotNull(
|
|
||||||
value = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
|
||||||
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
|
|
||||||
),
|
|
||||||
lazyMessage = {
|
|
||||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return storedTokens.tokens.any {
|
|
||||||
it.contractAddress != null &&
|
|
||||||
it.networkId == network.backendId &&
|
|
||||||
it.derivationPath == network.derivationPath.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>> {
|
override fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>> {
|
||||||
return userWalletsStore.userWallets.flatMapLatest { userWallets ->
|
return userWalletsStore.userWallets.flatMapLatest { userWallets ->
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ dependencies {
|
||||||
api(projects.domain.core)
|
api(projects.domain.core)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
|
implementation(projects.domain.tokens)
|
||||||
implementation(projects.domain.card)
|
implementation(projects.domain.card)
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tangem.domain.managetokens
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
|
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
|
import com.tangem.domain.tokens.model.Network
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
|
||||||
|
class CheckHasLinkedTokensUseCase(
|
||||||
|
private val repository: ManageTokensRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
network: Network,
|
||||||
|
tempAddedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
|
tempRemovedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
|
): Either<Throwable, Boolean> {
|
||||||
|
return Either.catch {
|
||||||
|
repository.hasLinkedTokens(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
network = network,
|
||||||
|
tempAddedTokens = tempAddedTokens,
|
||||||
|
tempRemovedTokens = tempRemovedTokens,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,19 @@ import arrow.core.Either
|
||||||
import arrow.core.flatten
|
import arrow.core.flatten
|
||||||
import com.tangem.domain.card.repository.DerivationsRepository
|
import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.tokens.model.Network
|
||||||
|
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.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
|
||||||
class SaveManagedTokensUseCase(
|
class SaveManagedTokensUseCase(
|
||||||
private val manageTokensRepository: ManageTokensRepository,
|
private val customTokensRepository: CustomTokensRepository,
|
||||||
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
|
private val networksRepository: NetworksRepository,
|
||||||
private val derivationsRepository: DerivationsRepository,
|
private val derivationsRepository: DerivationsRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -19,15 +26,102 @@ class SaveManagedTokensUseCase(
|
||||||
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
): Either<Throwable, Unit> {
|
): Either<Throwable, Unit> {
|
||||||
return Either.catch {
|
return Either.catch {
|
||||||
derivationsRepository.derivePublicKeysByNetworks(
|
// TODO: Currently order is matter. [REDACTED_JIRA]
|
||||||
userWalletId = userWalletId,
|
removeCurrencies(userWalletId, currenciesToRemove)
|
||||||
networks = currenciesToAdd.values.flatten(),
|
addCurrencies(userWalletId, currenciesToAdd)
|
||||||
)
|
}
|
||||||
manageTokensRepository.saveManagedCurrencies(
|
}
|
||||||
userWalletId = userWalletId,
|
|
||||||
currenciesToAdd = currenciesToAdd,
|
private suspend fun removeCurrencies(
|
||||||
currenciesToRemove = currenciesToRemove,
|
userWalletId: UserWalletId,
|
||||||
)
|
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
|
) {
|
||||||
|
if (currenciesToRemove.isEmpty()) return
|
||||||
|
|
||||||
|
val currencies = currenciesToRemove.mapToCryptoCurrencies()
|
||||||
|
currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies)
|
||||||
|
|
||||||
|
walletManagersFacade.remove(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
networks = currencies
|
||||||
|
.filterIsInstance<CryptoCurrency.Coin>()
|
||||||
|
.mapTo(hashSetOf(), CryptoCurrency::network),
|
||||||
|
)
|
||||||
|
|
||||||
|
walletManagersFacade.removeTokens(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
tokens = currencies.filterIsInstance<CryptoCurrency.Token>().toSet(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun addCurrencies(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
currenciesToAdd: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
|
) {
|
||||||
|
if (currenciesToAdd.isEmpty()) return
|
||||||
|
|
||||||
|
val existingCurrencies = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||||
|
val currencies = currenciesToAdd.mapToCryptoCurrencies()
|
||||||
|
derivationsRepository.derivePublicKeysByNetworks(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
networks = currenciesToAdd.values.flatten(),
|
||||||
|
)
|
||||||
|
currenciesRepository.addCurrencies(userWalletId, currencies)
|
||||||
|
refreshUpdatedNetworks(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
existingCurrencies = existingCurrencies,
|
||||||
|
currenciesToAdd = currencies,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun refreshUpdatedNetworks(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
currenciesToAdd: List<CryptoCurrency>,
|
||||||
|
existingCurrencies: List<CryptoCurrency>,
|
||||||
|
) {
|
||||||
|
val networksToUpdate = currenciesToAdd
|
||||||
|
.asSequence()
|
||||||
|
.filterIsInstance<CryptoCurrency.Token>()
|
||||||
|
.map(CryptoCurrency.Token::network)
|
||||||
|
.filterTo(hashSetOf()) { hasCoinForNetwork(existingCurrencies, it) }
|
||||||
|
|
||||||
|
val networkToUpdate = currenciesToAdd.map { it.network }
|
||||||
|
.subtract(existingCurrencies.map { it.network }.toSet())
|
||||||
|
|
||||||
|
networksRepository.getNetworkStatusesSync(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
networks = networksToUpdate + networkToUpdate,
|
||||||
|
refresh = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the [existingCurrencies] list contains a coin that corresponds
|
||||||
|
* to the given [network].
|
||||||
|
*/
|
||||||
|
private fun hasCoinForNetwork(existingCurrencies: List<CryptoCurrency>, network: Network): Boolean {
|
||||||
|
return existingCurrencies.any { currency ->
|
||||||
|
currency is CryptoCurrency.Coin && currency.network == network
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Map<ManagedCryptoCurrency.Token, Set<Network>>.mapToCryptoCurrencies(): List<CryptoCurrency> {
|
||||||
|
return flatMap { (token, networks) ->
|
||||||
|
token.availableNetworks
|
||||||
|
.filter { sourceNetwork -> networks.contains(sourceNetwork.network) }
|
||||||
|
.map { sourceNetwork ->
|
||||||
|
when (sourceNetwork) {
|
||||||
|
is ManagedCryptoCurrency.SourceNetwork.Default -> customTokensRepository.createToken(
|
||||||
|
managedCryptoCurrency = token,
|
||||||
|
sourceNetwork = sourceNetwork,
|
||||||
|
rawId = token.id.value,
|
||||||
|
)
|
||||||
|
is ManagedCryptoCurrency.SourceNetwork.Main -> customTokensRepository.createCoin(
|
||||||
|
networkId = sourceNetwork.id,
|
||||||
|
derivationPath = sourceNetwork.network.derivationPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +26,12 @@ interface CustomTokensRepository {
|
||||||
|
|
||||||
fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin
|
fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin
|
||||||
|
|
||||||
|
fun createToken(
|
||||||
|
managedCryptoCurrency: ManagedCryptoCurrency.Token,
|
||||||
|
sourceNetwork: ManagedCryptoCurrency.SourceNetwork.Default,
|
||||||
|
rawId: String?,
|
||||||
|
): CryptoCurrency.Token
|
||||||
|
|
||||||
suspend fun createCustomToken(
|
suspend fun createCustomToken(
|
||||||
networkId: Network.ID,
|
networkId: Network.ID,
|
||||||
derivationPath: Network.DerivationPath,
|
derivationPath: Network.DerivationPath,
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ interface ManageTokensRepository {
|
||||||
|
|
||||||
fun getTokenListBatchFlow(context: ManageTokensListBatchingContext, batchSize: Int): ManageTokensListBatchFlow
|
fun getTokenListBatchFlow(context: ManageTokensListBatchingContext, batchSize: Int): ManageTokensListBatchFlow
|
||||||
|
|
||||||
suspend fun saveManagedCurrencies(
|
suspend fun hasLinkedTokens(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
currenciesToAdd: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
network: Network,
|
||||||
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
tempAddedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
)
|
tempRemovedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
|
||||||
|
): Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package com.tangem.domain.tokens
|
|
||||||
|
|
||||||
import arrow.core.Either
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
|
||||||
|
|
||||||
class CheckHasLinkedTokensUseCase(
|
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<Throwable, Boolean> {
|
|
||||||
return Either.catch {
|
|
||||||
currenciesRepository.hasTokens(userWalletId, network)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -234,8 +234,6 @@ interface CurrenciesRepository {
|
||||||
networkId: String,
|
networkId: String,
|
||||||
): CryptoCurrency.Token
|
): CryptoCurrency.Token
|
||||||
|
|
||||||
suspend fun hasTokens(userWalletId: UserWalletId, network: Network): Boolean
|
|
||||||
|
|
||||||
/** Get crypto currencies by [currencyRawId] from all user wallets */
|
/** Get crypto currencies by [currencyRawId] from all user wallets */
|
||||||
fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>>
|
fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>>
|
||||||
}
|
}
|
||||||
|
|
@ -150,10 +150,6 @@ internal class MockCurrenciesRepository(
|
||||||
error("not implemented")
|
error("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hasTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>> {
|
override fun getAllWalletsCryptoCurrencies(currencyRawId: String): Flow<Map<UserWallet, List<CryptoCurrency>>> {
|
||||||
return emptyFlow()
|
return emptyFlow()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext
|
||||||
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
||||||
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
||||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
import com.tangem.domain.tokens.CheckHasLinkedTokensUseCase
|
import com.tangem.domain.managetokens.CheckHasLinkedTokensUseCase
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||||
|
|
@ -198,7 +198,12 @@ internal class ManageTokensListManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
override suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
||||||
return checkHasLinkedTokensUseCase(userWalletId, network).getOrElse {
|
return checkHasLinkedTokensUseCase(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
network = network,
|
||||||
|
tempAddedTokens = changedCurrenciesManager.currenciesToAdd.value,
|
||||||
|
tempRemovedTokens = changedCurrenciesManager.currenciesToRemove.value,
|
||||||
|
).getOrElse {
|
||||||
Timber.e(
|
Timber.e(
|
||||||
it,
|
it,
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue