Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-29 17:08:18 +03:00
commit 1bac8466b1
990 changed files with 22183 additions and 7489 deletions

View file

@ -2,21 +2,20 @@ package com.tangem.data.managetokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.crypto.hdWallet.DerivationPath
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.getNetwork
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.managetokens.utils.TokenAddressesConverter
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.extensions.canHandleBlockchain
import com.tangem.domain.common.extensions.supportedBlockchains
@ -24,14 +23,14 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
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.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber
@Suppress("LongParameterList")
internal class DefaultCustomTokensRepository(
private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore,
@ -39,16 +38,17 @@ internal class DefaultCustomTokensRepository(
private val walletManagersFacade: WalletManagersFacade,
private val excludedBlockchains: ExcludedBlockchains,
private val dispatchers: CoroutineDispatcherProvider,
private val userTokensSaver: UserTokensSaver,
private val networkFactory: NetworkFactory,
) : CustomTokensRepository {
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
private val userTokensResponseFactory = UserTokensResponseFactory()
private val tokenAddressConverter = TokenAddressesConverter()
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean =
withContext(dispatchers.io) {
when (val blockchain = Blockchain.fromId(networkId.value)) {
when (val blockchain = networkId.toBlockchain()) {
Blockchain.Unknown,
Blockchain.Binance,
Blockchain.BinanceTestnet,
@ -76,7 +76,7 @@ internal class DefaultCustomTokensRepository(
}
storedCurrencies.tokens.none { token ->
Blockchain.fromId(networkId.value).toNetworkId() == token.networkId &&
networkId.toBlockchain().toNetworkId() == token.networkId &&
derivationPath.value == token.derivationPath &&
contractAddress.equals(token.contractAddress, ignoreCase = true)
}
@ -93,7 +93,11 @@ internal class DefaultCustomTokensRepository(
"User wallet [$userWalletId] not found while finding token"
}
val network = requireNotNull(
getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains),
networkFactory.create(
networkId = networkId,
derivationPath = derivationPath,
scanResponse = userWallet.scanResponse,
),
) {
"Network [$networkId] not found while finding token"
}
@ -145,7 +149,11 @@ internal class DefaultCustomTokensRepository(
"User wallet [$userWalletId] not found while creating coin"
}
val network = requireNotNull(
getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains),
networkFactory.create(
networkId = networkId,
derivationPath = derivationPath,
scanResponse = userWallet.scanResponse,
),
) {
"Network [$networkId] not found while creating coin"
}
@ -178,7 +186,11 @@ internal class DefaultCustomTokensRepository(
"User wallet [$userWalletId] not found while creating custom token"
}
val network = requireNotNull(
getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains),
networkFactory.create(
networkId = networkId,
derivationPath = derivationPath,
scanResponse = userWallet.scanResponse,
),
) {
"Network [$networkId] not found while creating custom token"
}
@ -223,7 +235,7 @@ internal class DefaultCustomTokensRepository(
}
val token = userTokensResponseFactory.createResponseToken(cryptoCurrency)
storeAndPushTokens(
userTokensSaver.storeAndPush(
userWalletId = userWalletId,
response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }),
)
@ -248,11 +260,10 @@ internal class DefaultCustomTokensRepository(
)
if (canHandleBlockchain) {
getNetwork(
networkFactory.create(
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
excludedBlockchains = excludedBlockchains,
)
} else {
null
@ -267,20 +278,4 @@ internal class DefaultCustomTokensRepository(
value = sdkPath.rawPath,
)
}
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
appPreferencesStore.storeObject(
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
value = compatibleUserTokensResponse,
)
pushTokens(userWalletId, response)
}
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}")
}
}
}

View file

@ -4,14 +4,15 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.currency.getBlockchain
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.common.utils.retryOnError
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
@ -26,11 +27,10 @@ import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.supportedTokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.managetokens.model.*
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.pagination.BatchFetchResult
@ -48,12 +48,13 @@ internal class DefaultManageTokensRepository(
private val appPreferencesStore: AppPreferencesStore,
private val testnetTokensStorage: TestnetTokensStorage,
private val excludedBlockchains: ExcludedBlockchains,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider,
networkFactory: NetworkFactory,
) : ManageTokensRepository {
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains)
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(networkFactory, excludedBlockchains)
private val userTokensResponseFactory = UserTokensResponseFactory()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains)
// region getTokenListBatchFlow
override fun getTokenListBatchFlow(
@ -77,7 +78,7 @@ internal class DefaultManageTokensRepository(
prefetchDistance = batchSize,
batchSize = batchSize,
subFetcher = { request, _, isFirstBatchFetching ->
val userWallet = request.params.userWalletId?.let { getUserWallet(it) }
val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict)
if (userWallet?.scanResponse?.card?.isTestCard == true) {
fetchTestnetCurrencies(userWallet, request)
@ -190,17 +191,11 @@ internal class DefaultManageTokensRepository(
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
userTokensResponseFactory.createUserTokensResponse(
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
}
}
private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> {
return userWallet?.scanResponse?.let {
it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains)
@ -237,10 +232,11 @@ internal class DefaultManageTokensRepository(
token.availableNetworks
.filter { sourceNetwork -> networks.contains(sourceNetwork.network) }
.map { sourceNetwork ->
val blockchain = getBlockchain(sourceNetwork.network.id)
val networkId = sourceNetwork.network.toBlockchain().toNetworkId()
UserTokensResponse.Token(
id = token.id.value,
networkId = blockchain.toNetworkId(),
networkId = networkId,
derivationPath = sourceNetwork.network.derivationPath.value,
name = token.name,
symbol = token.symbol,
@ -261,8 +257,9 @@ internal class DefaultManageTokensRepository(
userWalletId: UserWalletId,
sourceNetwork: SourceNetwork,
): CurrencyUnsupportedState? {
val userWallet = getUserWallet(userWalletId = userWalletId)
val blockchain = getBlockchain(sourceNetwork.id)
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = sourceNetwork.id.toBlockchain()
return when (sourceNetwork) {
is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain)
is SourceNetwork.Main -> checkBlockchainUnsupportedState(userWallet = userWallet, blockchain = blockchain)
@ -274,7 +271,7 @@ internal class DefaultManageTokensRepository(
rawNetworkId: String,
isMainNetwork: Boolean,
): CurrencyUnsupportedState? {
val userWallet = getUserWallet(userWalletId = userWalletId)
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId)
?: error("Can not create blockchain with given networkId -> $rawNetworkId")
return if (isMainNetwork) {

View file

@ -1,6 +1,9 @@
package com.tangem.data.managetokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.managetokens.DefaultCustomTokensRepository
import com.tangem.data.managetokens.DefaultManageTokensRepository
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
@ -32,15 +35,19 @@ internal object ManageTokensDataModule {
testnetTokensStorage: TestnetTokensStorage,
dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
networkFactory: NetworkFactory,
): ManageTokensRepository {
return DefaultManageTokensRepository(
tangemTechApi,
userWalletsStore,
manageTokensUpdateFetcher,
appPreferencesStore,
testnetTokensStorage,
excludedBlockchains,
dispatchers,
tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore,
manageTokensUpdateFetcher = manageTokensUpdateFetcher,
appPreferencesStore = appPreferencesStore,
testnetTokensStorage = testnetTokensStorage,
excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
networkFactory = networkFactory,
dispatchers = dispatchers,
)
}
@ -53,14 +60,18 @@ internal object ManageTokensDataModule {
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
userTokensSaver: UserTokensSaver,
networkFactory: NetworkFactory,
): CustomTokensRepository {
return DefaultCustomTokensRepository(
tangemTechApi,
userWalletsStore,
appPreferencesStore,
walletManagersFacade,
excludedBlockchains,
dispatchers,
tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore,
appPreferencesStore = appPreferencesStore,
walletManagersFacade = walletManagersFacade,
excludedBlockchains = excludedBlockchains,
dispatchers = dispatchers,
userTokensSaver = userTokensSaver,
networkFactory = networkFactory,
)
}
}

View file

@ -8,8 +8,8 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.data.common.currency.getCoinId
import com.tangem.data.common.currency.getNetwork
import com.tangem.data.common.currency.getTokenId
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
@ -19,12 +19,13 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import timber.log.Timber
internal class ManagedCryptoCurrencyFactory(
private val networkFactory: NetworkFactory,
private val excludedBlockchains: ExcludedBlockchains,
) {
@ -98,11 +99,10 @@ internal class ManagedCryptoCurrencyFactory(
return null
}
val network = getNetwork(
val network = networkFactory.create(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
scanResponse = scanResponse,
excludedBlockchains = excludedBlockchains,
) ?: return null
val contractAddress = token.contractAddress
@ -172,11 +172,10 @@ internal class ManagedCryptoCurrencyFactory(
?.takeUnless { it in excludedBlockchains }
?: return null
val network = getNetwork(
blockchain,
extraDerivationPath,
scanResponse?.derivationStyleProvider,
excludedBlockchains,
val network = networkFactory.create(
blockchain = blockchain,
extraDerivationPath = extraDerivationPath,
derivationStyleProvider = scanResponse?.derivationStyleProvider,
canHandleTokens = scanResponse?.let {
it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains)
} ?: true,
@ -217,11 +216,10 @@ internal class ManagedCryptoCurrencyFactory(
val blockchain = Blockchain.fromNetworkId(token.networkId)
if (blockchain != null && blockchain !in excludedBlockchains) {
getNetwork(
blockchain,
token.derivationPath,
scanResponse?.derivationStyleProvider,
excludedBlockchains,
networkFactory.create(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
derivationStyleProvider = scanResponse?.derivationStyleProvider,
canHandleTokens = scanResponse?.let {
it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains)
} ?: true,

View file

@ -4,8 +4,8 @@ import com.tangem.blockchain.blockchains.cardano.CardanoTokenAddressConverter
import com.tangem.blockchain.blockchains.hedera.HederaTokenAddressConverter
import com.tangem.blockchain.blockchains.sui.SuiTokenAddressConverter
import com.tangem.blockchain.common.Blockchain
import com.tangem.data.common.currency.getBlockchain
import com.tangem.domain.tokens.model.Network
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.models.network.Network
internal class TokenAddressesConverter {
private val hederaTokenAddressConverter = HederaTokenAddressConverter()
@ -13,7 +13,7 @@ internal class TokenAddressesConverter {
private val suiTokenAddressConverter = SuiTokenAddressConverter()
fun convertTokenAddress(networkId: Network.ID, contractAddress: String, symbol: String?): String {
val convertedAddress = when (getBlockchain(networkId)) {
val convertedAddress = when (networkId.toBlockchain()) {
Blockchain.Hedera,
Blockchain.HederaTestnet,
-> hederaTokenAddressConverter.convertToTokenId(contractAddress)