Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-12 15:33:35 +04:00
parent 516d72cb0c
commit debf9ccd90
14 changed files with 158 additions and 150 deletions

View file

@ -1,14 +1,32 @@
package com.tangem.data.onramp
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.data.onramp.converters.HotCryptoCurrencyConverter
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.exchangeservice.hotcrypto.HotCryptoResponseStore
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.userwallet.UserWalletsStore
import com.tangem.domain.onramp.model.HotCryptoCurrency
import com.tangem.domain.onramp.repositories.HotCryptoRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.runCatching
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.*
import timber.log.Timber
/**
* Default implementation of [HotCryptoRepository]
@ -16,17 +34,49 @@ import kotlinx.coroutines.flow.map
* @property excludedBlockchains excluded blockchains
* @property hotCryptoResponseStore store of `HotCryptoResponse`
* @property userWalletsStore store of `UserWallet`
* @property tangemTechApi tangem tech api
* @property appPreferencesStore app preferences store
* @property dispatchers dispatchers
* @property analyticsEventHandler analytics event handler
*
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
@OptIn(ExperimentalCoroutinesApi::class)
internal class DefaultHotCryptoRepository(
private val excludedBlockchains: ExcludedBlockchains,
private val hotCryptoResponseStore: HotCryptoResponseStore,
private val userWalletsStore: UserWalletsStore,
private val tangemTechApi: TangemTechApi,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : HotCryptoRepository {
private val coroutineScope = CoroutineScope(dispatchers.main + SupervisorJob())
private val hotCryptoJobHolder = JobHolder()
override fun fetchHotCrypto() {
combine(
flow = getWalletsWithTokensFlow(),
flow2 = getHotCryptoFlow(),
) { walletsWithTokens, hotCrypto ->
hotCrypto ?: return@combine emptyMap()
walletsWithTokens.mapValues { (_, tokens) ->
hotCrypto.filterTokens(tokens)
}
}
.onEach(hotCryptoResponseStore::store)
.launchIn(scope = coroutineScope)
.saveIn(jobHolder = hotCryptoJobHolder)
}
override fun getCurrencies(userWalletId: UserWalletId): Flow<List<HotCryptoCurrency>> {
return hotCryptoResponseStore.get()
.map { it[userWalletId] }
.filterNotNull()
.map {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId)
?: error("UserWalletId [$userWalletId] not found")
@ -40,4 +90,53 @@ internal class DefaultHotCryptoRepository(
.filterNotNull()
}
}
private fun getWalletsWithTokensFlow(): Flow<Map<UserWalletId, List<UserTokensResponse.Token>>> {
return userWalletsStore.userWallets.flatMapLatest { userWallets ->
val flows = userWallets.map { userWallet ->
appPreferencesStore.getObject<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
)
.map { userWallet.walletId to it?.tokens.orEmpty() }
}
combine(flows) { it.toMap() }
}
}
private fun getHotCryptoFlow(): Flow<HotCryptoResponse?> {
return appPreferencesStore
.getObject<CurrenciesResponse.Currency>(key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY)
.map { it?.id ?: "usd" }
.distinctUntilChanged()
.map { getHotCrypto(appCurrencyId = it).getOrNull() }
.distinctUntilChanged()
}
private suspend fun getHotCrypto(appCurrencyId: String): Result<HotCryptoResponse> {
return runCatching(dispatchers.io) {
tangemTechApi.getHotCrypto(currencyId = appCurrencyId).getOrThrow()
}
.onSuccess { Timber.d("HotCrypto is successfully updated") }
.onFailure {
Timber.e(it, "Unable to fetch hot crypto")
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.HotTokenError(
errorCode = (it as? ApiResponseError.HttpException)?.code?.code?.toString().orEmpty(),
),
)
}
}
private fun HotCryptoResponse.filterTokens(tokens: List<UserTokensResponse.Token>): HotCryptoResponse {
return copy(
tokens = this.tokens.filter { hotToken ->
tokens.none {
it.id == hotToken.id && it.contractAddress == hotToken.contractAddress &&
it.networkId == hotToken.networkId
}
},
)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.data.onramp.di
import com.squareup.moshi.Moshi
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.onramp.DefaultHotCryptoRepository
import com.tangem.data.onramp.DefaultOnrampErrorResolver
import com.tangem.data.onramp.DefaultOnrampRepository
@ -10,6 +11,7 @@ import com.tangem.data.onramp.converters.error.OnrampErrorConverter
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.exchangeservice.hotcrypto.HotCryptoResponseStore
@ -95,11 +97,19 @@ internal object OnrampDataModule {
excludedBlockchains: ExcludedBlockchains,
hotCryptoResponseStore: HotCryptoResponseStore,
userWalletsStore: UserWalletsStore,
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
analyticsEventHandler: AnalyticsEventHandler,
): HotCryptoRepository {
return DefaultHotCryptoRepository(
excludedBlockchains = excludedBlockchains,
hotCryptoResponseStore = hotCryptoResponseStore,
userWalletsStore = userWalletsStore,
tangemTechApi = tangemTechApi,
appPreferencesStore = appPreferencesStore,
dispatchers = dispatchers,
analyticsEventHandler = analyticsEventHandler,
)
}
}