Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-26 13:14:53 +03:00
commit 43c376489a
316 changed files with 7586 additions and 3131 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.data.feedback.converters
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.util.getBackupCardsCount
import com.tangem.domain.feedback.models.CardInfo
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
@ -20,10 +21,7 @@ internal object CardInfoConverter : Converter<ScanResponse, CardInfo> {
CardInfo(
userWalletId = createUserWalletId(scanResponse = value),
cardId = card.cardId,
cardsCount = when (val status = value.card.backupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount.toString()
else -> "0"
},
cardsCount = value.getBackupCardsCount()?.toString() ?: "0",
firmwareVersion = card.firmwareVersion.stringValue,
cardBlockchain = walletData?.blockchain,
signedHashesList = card.wallets.map {

View file

@ -108,6 +108,22 @@ internal class DefaultMarketsTokenRepository(
return TokenChartConverter.convert(interval, response.getOrThrow())
}
override suspend fun getChartPreview(
fiatCurrencyCode: String,
interval: PriceChangeInterval,
tokenId: String,
): TokenChart {
val response = marketsApi.getCoinsListCharts(
coinIds = tokenId,
currency = fiatCurrencyCode,
interval = interval.toRequestParam(),
)
val chart = response.getOrThrow()[tokenId] ?: error("No chart preview data for token $tokenId")
return TokenChartConverter.convert(interval, chart)
}
override suspend fun getTokenInfo(
fiatCurrencyCode: String,
tokenId: String,

View file

@ -2,10 +2,13 @@ package com.tangem.data.staking
import android.util.Base64
import arrow.core.raise.catch
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toCompressedPublicKey
@ -22,6 +25,7 @@ import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.request.*
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectListSync
@ -45,7 +49,6 @@ import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
@ -67,6 +70,7 @@ internal class DefaultStakingRepository(
private val dispatchers: CoroutineDispatcherProvider,
private val stakingFeatureToggle: StakingFeatureToggles,
private val walletManagersFacade: WalletManagersFacade,
moshi: Moshi,
) : StakingRepository {
private val stakingNetworkTypeConverter = StakingNetworkTypeConverter()
@ -104,6 +108,13 @@ internal class DefaultStakingRepository(
value = emptyMap<UserWalletId, Boolean>(),
)
private val tronStakeKitTransactionAdapter: JsonAdapter<TronStakeKitTransaction> =
moshi.adapter(TronStakeKitTransaction::class.java)
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) {
rawNetworkId.plus(rawCurrencyId)
}
override fun isStakingSupported(integrationKey: String): Boolean {
return integrationIdMap.containsKey(integrationKey)
}
@ -141,8 +152,8 @@ internal class DefaultStakingRepository(
val yield = getYield(cryptoCurrencyId, symbol)
StakingEntryInfo(
interestRate = requireNotNull(yield.validators.maxByOrNull { it.apr.orZero() }?.apr),
periodInDays = yield.metadata.cooldownPeriod.days,
apr = requireNotNull(yield.validators.maxByOrNull { it.apr.orZero() }?.apr),
rewardSchedule = yield.metadata.rewardSchedule,
tokenSymbol = yield.token.symbol,
)
}
@ -158,7 +169,7 @@ internal class DefaultStakingRepository(
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
val isSupported = isStakingSupported(cryptoCurrencyId.getIntegrationKey())
val isSupported = isStakingSupported(getIntegrationKey(cryptoCurrencyId))
when {
prefetchedYield != null && isSupported -> {
@ -261,25 +272,27 @@ internal class DefaultStakingRepository(
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
address: CryptoCurrencyAddress,
cryptoCurrency: CryptoCurrency,
refresh: Boolean,
) = withContext(dispatchers.io) {
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
val cryptoCurrency = address.cryptoCurrency
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()] ?: return@withContext
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] ?: return@withContext
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val requestBody = getBalanceRequestData(address.address, integrationId)
val requestBody = getBalanceRequestData(address, integrationId)
val result = stakeKitApi.getSingleYieldBalance(
integrationId = requestBody.integrationId,
body = requestBody,
).getOrThrow()
stakingBalanceStore.store(
userWalletId,
requestBody.integrationId,
YieldBalanceWrapperDTO(
balances = result,
@ -292,15 +305,15 @@ internal class DefaultStakingRepository(
override fun getSingleYieldBalanceFlow(
userWalletId: UserWalletId,
address: CryptoCurrencyAddress,
cryptoCurrency: CryptoCurrency,
): Flow<YieldBalance> = channelFlow {
if (!stakingFeatureToggle.isStakingEnabled) {
send(YieldBalance.Empty)
} else {
launch(dispatchers.io) {
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
stakingBalanceStore.get(integrationId)
stakingBalanceStore.get(userWalletId, integrationId)
.collectLatest {
send(
yieldBalanceConverter.convert(
@ -316,7 +329,7 @@ internal class DefaultStakingRepository(
withContext(dispatchers.io) {
fetchSingleYieldBalance(
userWalletId,
address,
cryptoCurrency,
)
}
}
@ -324,16 +337,18 @@ internal class DefaultStakingRepository(
override suspend fun getSingleYieldBalanceSync(
userWalletId: UserWalletId,
address: CryptoCurrencyAddress,
cryptoCurrency: CryptoCurrency,
): YieldBalance = withContext(dispatchers.io) {
if (!stakingFeatureToggle.isStakingEnabled) {
YieldBalance.Empty
} else {
fetchSingleYieldBalance(userWalletId, address)
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
val result = stakingBalanceStore.getSyncOrNull(integrationId) ?: return@withContext YieldBalance.Error
val result = stakingBalanceStore.getSyncOrNull(userWalletId, integrationId)
?: return@withContext YieldBalance.Error
yieldBalanceConverter.convert(
YieldBalanceConverter.Data(
balance = result,
@ -345,7 +360,7 @@ internal class DefaultStakingRepository(
override suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
addresses: List<CryptoCurrencyAddress>,
cryptoCurrencies: List<CryptoCurrency>,
refresh: Boolean,
) = withContext(dispatchers.io) {
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
@ -357,23 +372,23 @@ internal class DefaultStakingRepository(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val result = stakeKitApi.getMultipleYieldBalances(
addresses
.mapNotNull { networkAddress ->
val cryptoCurrency = networkAddress.cryptoCurrency
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()]
val availableCurrencies = cryptoCurrencies
.mapNotNull { currency ->
val address = walletManagersFacade.getDefaultAddress(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null) {
networkAddress.address to integrationId
} else {
null
}
if (integrationId != null && address != null) {
address to integrationId
} else {
null
}
.distinct()
.map { getBalanceRequestData(it.first, it.second) },
).getOrThrow()
}
.distinct()
.map { getBalanceRequestData(it.first, it.second) }
.ifEmpty { return@invokeOnExpire }
val result = stakeKitApi.getMultipleYieldBalances(availableCurrencies).getOrThrow()
stakingBalanceStore.store(result)
stakingBalanceStore.store(userWalletId, result)
},
)
} finally {
@ -385,20 +400,20 @@ internal class DefaultStakingRepository(
override fun getMultiYieldBalanceFlow(
userWalletId: UserWalletId,
addresses: List<CryptoCurrencyAddress>,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> = channelFlow {
if (!stakingFeatureToggle.isStakingEnabled) {
send(YieldBalanceList.Empty)
} else {
launch(dispatchers.io) {
stakingBalanceStore.get()
stakingBalanceStore.get(userWalletId)
.collectLatest { send(yieldBalanceListConverter.convert(it)) }
}
withContext(dispatchers.io) {
fetchMultiYieldBalance(
userWalletId,
addresses,
cryptoCurrencies,
)
}
}
@ -406,14 +421,14 @@ internal class DefaultStakingRepository(
override fun getMultiYieldBalanceLce(
userWalletId: UserWalletId,
addresses: List<CryptoCurrencyAddress>,
cryptoCurrencies: List<CryptoCurrency>,
): LceFlow<Throwable, YieldBalanceList> = lceFlow {
if (!stakingFeatureToggle.isStakingEnabled) {
send(YieldBalanceList.Empty)
} else {
launch(dispatchers.io) {
combine(
stakingBalanceStore.get(),
stakingBalanceStore.get(userWalletId),
isYieldBalanceFetching.map { it.getOrElse(userWalletId) { false } },
) { result, isFetching ->
val balances = yieldBalanceListConverter.convert(result)
@ -422,7 +437,7 @@ internal class DefaultStakingRepository(
}
withContext(dispatchers.io) {
catch(
block = { fetchMultiYieldBalance(userWalletId, addresses, refresh = false) },
block = { fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) },
catch = { raise(it) },
)
}
@ -431,13 +446,13 @@ internal class DefaultStakingRepository(
override suspend fun getMultiYieldBalanceSync(
userWalletId: UserWalletId,
addresses: List<CryptoCurrencyAddress>,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = withContext(dispatchers.io) {
if (!stakingFeatureToggle.isStakingEnabled) {
YieldBalanceList.Empty
} else {
fetchMultiYieldBalance(userWalletId, addresses)
val result = stakingBalanceStore.getSyncOrNull() ?: return@withContext YieldBalanceList.Error
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
yieldBalanceListConverter.convert(result)
}
}
@ -506,6 +521,8 @@ internal class DefaultStakingRepository(
amount = params.amount.toPlainString(),
inputToken = tokenConverter.convertBack(params.token),
validatorAddress = params.validatorAddress,
validatorAddresses = listOf(params.validatorAddress), // check on other networks
tronResource = getTronResource(network),
),
)
}
@ -539,16 +556,8 @@ internal class DefaultStakingRepository(
}
}
override fun isStakeMoreAvailable(networkId: Network.ID): Boolean {
val blockchain = Blockchain.fromId(networkId.value)
return when (blockchain) {
Blockchain.Solana -> false
else -> true
}
}
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval {
return when (cryptoCurrency.id.getIntegrationKey()) {
return when (getIntegrationKey(cryptoCurrency.id)) {
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() -> {
StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
}
@ -562,8 +571,14 @@ internal class DefaultStakingRepository(
Blockchain.Solana,
Blockchain.Cosmos,
-> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes())
Blockchain.BSC,
Blockchain.Ethereum,
-> TransactionData.Compiled.Data.RawString(unsignedTransaction)
Blockchain.Tron -> {
val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction)
?: error("Failed to parse Tron StakeKit transaction")
TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex)
}
else -> error("Unsupported blockchain")
}
}
@ -593,7 +608,15 @@ internal class DefaultStakingRepository(
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
private fun CryptoCurrency.ID.getIntegrationKey(): String = rawNetworkId.plus(rawCurrencyId)
private fun getTronResource(network: Network): TronResource? {
val blockchain = Blockchain.fromNetworkId(network.backendId)
return if (blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet) {
TronResource.ENERGY
} else {
null
}
}
private companion object {
const val YIELDS_STORE_KEY = "yields"
@ -601,11 +624,11 @@ internal class DefaultStakingRepository(
const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
const val ETHEREUM_POLYGON_INTEGRATION_ID = "ethereum-matic-native-staking"
const val BINANCE_INTEGRATION_ID = "bsc-bnb-native-staking"
const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
const val BINANCE_INTEGRATION_ID = "bsc-bnb-native-staking"
const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
const val NEAR_INTEGRATION_ID = "near-near-native-staking"
const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
@ -617,11 +640,11 @@ internal class DefaultStakingRepository(
Blockchain.Solana.run { id + toCoinId() } to SOLANA_INTEGRATION_ID,
Blockchain.Cosmos.run { id + toCoinId() } to COSMOS_INTEGRATION_ID,
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
// Blockchain.Polkadot.run { id + toCoinId() } to POLKADOT_INTEGRATION_ID,
// Blockchain.Avalanche.run { id + toCoinId() } to AVALANCHE_INTEGRATION_ID,
// Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID,
Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID,
// Blockchain.Cronos.run { id + toCoinId() } to CRONOS_INTEGRATION_ID,
// Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
// Blockchain.Kava.run { id + toCoinId() } to KAVA_INTEGRATION_ID,
// Blockchain.Near.run { id + toCoinId() } to NEAR_INTEGRATION_ID,
// Blockchain.Tezos.run { id + toCoinId() } to TEZOS_INTEGRATION_ID,

View file

@ -26,6 +26,7 @@ internal class YieldBalanceConverter : Converter<YieldBalanceConverter.Data, Yie
rawCurrencyId = item.tokenDTO.coinGeckoId,
rawNetworkId = item.tokenDTO.network.name,
validatorAddress = item.validatorAddress,
date = item.date?.toDateTime(),
pendingActions = pendingActionConverter.convertList(item.pendingActions),
)
},

View file

@ -4,13 +4,13 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrap
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.utils.converter.Converter
internal class YieldBalanceListConverter : Converter<List<YieldBalanceWrapperDTO>, YieldBalanceList> {
internal class YieldBalanceListConverter : Converter<Set<YieldBalanceWrapperDTO>, YieldBalanceList> {
internal val converter by lazy(LazyThreadSafetyMode.NONE) {
YieldBalanceConverter()
}
override fun convert(value: List<YieldBalanceWrapperDTO>): YieldBalanceList {
override fun convert(value: Set<YieldBalanceWrapperDTO>): YieldBalanceList {
return if (value.isEmpty()) {
YieldBalanceList.Empty
} else {

View file

@ -37,6 +37,7 @@ internal object StakingDataModule {
stakingFeatureToggle: StakingFeatureToggles,
cacheRegistry: CacheRegistry,
walletManagersFacade: WalletManagersFacade,
@NetworkMoshi moshi: Moshi,
): StakingRepository {
return DefaultStakingRepository(
stakeKitApi = stakeKitApi,
@ -47,6 +48,7 @@ internal object StakingDataModule {
cacheRegistry = cacheRegistry,
stakingFeatureToggle = stakingFeatureToggle,
walletManagersFacade = walletManagersFacade,
moshi = moshi,
)
}

View file

@ -8,7 +8,6 @@ import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -28,7 +27,7 @@ internal object TokensDataModule {
fun provideCurrenciesRepository(
tangemTechApi: TangemTechApi,
tangemExpressApi: TangemExpressApi,
userTokensStore: UserTokensStore,
appPreferencesStore: AppPreferencesStore,
userWalletsStore: UserWalletsStore,
walletManagersFacade: WalletManagersFacade,
expressAssetsStore: ExpressAssetsStore,
@ -38,7 +37,7 @@ internal object TokensDataModule {
return DefaultCurrenciesRepository(
tangemTechApi = tangemTechApi,
tangemExpressApi = tangemExpressApi,
userTokensStore = userTokensStore,
appPreferencesStore = appPreferencesStore,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
expressAssetsStore = expressAssetsStore,
@ -71,7 +70,7 @@ internal object TokensDataModule {
networksStatusesStore: NetworksStatusesStore,
walletManagersFacade: WalletManagersFacade,
userWalletsStore: UserWalletsStore,
userTokensStore: UserTokensStore,
appPreferencesStore: AppPreferencesStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): NetworksRepository {
@ -79,7 +78,7 @@ internal object TokensDataModule {
networksStatusesStore = networksStatusesStore,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
userTokensStore = userTokensStore,
appPreferencesStore = appPreferencesStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)

View file

@ -18,8 +18,12 @@ 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.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
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.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
@ -46,11 +50,11 @@ import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
internal class DefaultCurrenciesRepository(
private val tangemTechApi: TangemTechApi,
private val tangemExpressApi: TangemExpressApi,
private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore,
private val walletManagersFacade: WalletManagersFacade,
private val expressAssetsStore: ExpressAssetsStore,
private val cacheRegistry: CacheRegistry,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : CurrenciesRepository {
@ -86,7 +90,7 @@ internal class DefaultCurrenciesRepository(
override suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
return withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
value = userTokensStore.getSyncOrNull(userWalletId),
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
)
@ -141,7 +145,7 @@ internal class DefaultCurrenciesRepository(
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) =
withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
value = userTokensStore.getSyncOrNull(userWalletId),
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform remove currency action" },
)
@ -163,7 +167,7 @@ internal class DefaultCurrenciesRepository(
override suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
return withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
value = userTokensStore.getSyncOrNull(userWalletId),
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform remove currencies action" },
)
@ -276,13 +280,31 @@ internal class DefaultCurrenciesRepository(
fetchTokensIfCacheExpired(userWallet, refresh)
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
val storedTokens = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWallet.walletId),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCurrenciesFactory.createCurrencies(storedTokens, userWallet.scanResponse)
}
override suspend fun getMultiCurrencyWalletCachedCurrenciesSync(userWalletId: UserWalletId) =
withContext(dispatchers.io) {
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
val storedTokens = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWallet.walletId),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCurrenciesFactory.createCurrencies(storedTokens, userWallet.scanResponse)
}
override suspend fun getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
@ -290,9 +312,12 @@ internal class DefaultCurrenciesRepository(
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
val response = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCurrenciesFactory.createCurrency(
currencyId = id,
@ -312,9 +337,12 @@ internal class DefaultCurrenciesRepository(
fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false)
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
val storedTokens = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
val blockchain = Blockchain.fromId(networkId.value)
val blockchainNetworkId = blockchain.toNetworkId()
val coinId = blockchain.toCoinId()
@ -335,7 +363,7 @@ internal class DefaultCurrenciesRepository(
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
launch(dispatchers.io) {
userTokensStore.get(userWalletId)
getSavedUserTokensResponse(userWalletId)
.map { it.group == UserTokensResponse.GroupType.NETWORK }
.collect(::send)
}
@ -347,7 +375,7 @@ internal class DefaultCurrenciesRepository(
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
launch(dispatchers.io) {
userTokensStore.get(userWalletId)
getSavedUserTokensResponse(userWalletId)
.map { it.sort == UserTokensResponse.SortType.BALANCE }
.collect(::send)
}
@ -461,9 +489,14 @@ internal class DefaultCurrenciesRepository(
val userWallet = getUserWallet(userWalletId)
fetchTokensIfCacheExpired(userWallet, refresh = false)
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
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 &&
@ -473,7 +506,7 @@ internal class DefaultCurrenciesRepository(
}
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
return getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies(
response = storedTokens,
scanResponse = userWallet.scanResponse,
@ -517,17 +550,26 @@ internal class DefaultCurrenciesRepository(
.let { customTokensMerger.mergeIfPresented(userWalletId, response) }
.let(userTokensBackwardCompatibility::applyCompatibilityAndGetUpdated)
userTokensStore.store(userWallet.walletId, compatibleUserTokensResponse)
appPreferencesStore.storeObject(
key = PreferencesKeys.getUserTokensKey(userWalletId = userWallet.walletId.stringValue),
value = compatibleUserTokensResponse,
)
fetchExchangeableUserMarketCoinsByIds(userWalletId, compatibleUserTokensResponse)
}
private suspend fun checkIsEmptyDemoWallet(userWallet: UserWallet): Boolean {
return demoConfig.isDemoCardId(userWallet.cardId) && userTokensStore.getSyncOrNull(userWallet.walletId) == null
val response = getSavedUserTokensResponseSync(key = userWallet.walletId)
return demoConfig.isDemoCardId(userWallet.cardId) && response == null
}
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
userTokensStore.store(userWalletId, compatibleUserTokensResponse)
appPreferencesStore.storeObject(
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
value = compatibleUserTokensResponse,
)
pushTokens(userWalletId, response)
}
@ -561,8 +603,9 @@ internal class DefaultCurrenciesRepository(
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
val userWalletId = userWallet.walletId
val response = userTokensStore.getSyncOrNull(userWalletId)
?: createDefaultUserTokensResponse(userWallet)
val response = appPreferencesStore.getObjectSyncOrNull(
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
) ?: createDefaultUserTokensResponse(userWallet)
if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
Timber.w(e, "Requested currencies could not be found in the remote store for: $userWalletId")
@ -622,4 +665,16 @@ internal class DefaultCurrenciesRepository(
}
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
private fun getSavedUserTokensResponse(key: UserWalletId): Flow<UserTokensResponse> {
return appPreferencesStore
.getObject<UserTokensResponse>(PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue))
.filterNotNull()
}
private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? {
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(key.stringValue),
)
}
}

View file

@ -8,8 +8,11 @@ import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkStatusFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.token.UserTokensStore
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.userwallet.UserWalletsStore
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.LceFlow
@ -33,7 +36,7 @@ internal class DefaultNetworksRepository(
private val networksStatusesStore: NetworksStatusesStore,
private val walletManagersFacade: WalletManagersFacade,
private val userWalletsStore: UserWalletsStore,
private val userTokensStore: UserTokensStore,
private val appPreferencesStore: AppPreferencesStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : NetworksRepository {
@ -300,9 +303,14 @@ internal class DefaultNetworksRepository(
}
return if (userWallet.isMultiCurrency) {
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
val response = requireNotNull(
value = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
} else {