Updated on 2026-08-14
This commit is contained in:
parent
e7b7c086bb
commit
ef8a13cfaa
14 changed files with 267 additions and 46 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "442ac578743a8b624777711cf49c77e2",
|
||||
"identityHash": "55f2651d215126dd0465b9c711165cba",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "express_provider",
|
||||
|
|
@ -474,11 +474,88 @@
|
|||
"address"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "onramp_country",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `name` TEXT NOT NULL, `image` TEXT NOT NULL, `alpha3` TEXT NOT NULL, `continent` TEXT NOT NULL, `onramp_available` INTEGER NOT NULL, `currency_name` TEXT NOT NULL, `currency_code` TEXT NOT NULL, `currency_image` TEXT, `currency_precision` INTEGER NOT NULL, `currency_unit` TEXT NOT NULL, PRIMARY KEY(`code`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "code",
|
||||
"columnName": "code",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "image",
|
||||
"columnName": "image",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "alpha3",
|
||||
"columnName": "alpha3",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "continent",
|
||||
"columnName": "continent",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "onrampAvailable",
|
||||
"columnName": "onramp_available",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultCurrency.name",
|
||||
"columnName": "currency_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultCurrency.code",
|
||||
"columnName": "currency_code",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultCurrency.image",
|
||||
"columnName": "currency_image",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultCurrency.precision",
|
||||
"columnName": "currency_precision",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultCurrency.unit",
|
||||
"columnName": "currency_unit",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '442ac578743a8b624777711cf49c77e2')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '55f2651d215126dd0465b9c711165cba')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.local.converter
|
||||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.OnrampCountryEntity
|
||||
|
||||
/** Maps an [OnrampCountryDTO] API response into its persisted [OnrampCountryEntity]. */
|
||||
fun OnrampCountryDTO.toEntity(): OnrampCountryEntity {
|
||||
return OnrampCountryEntity(
|
||||
code = code,
|
||||
name = name,
|
||||
image = image,
|
||||
alpha3 = alpha3,
|
||||
continent = continent,
|
||||
isOnrampAvailable = onrampAvailable,
|
||||
defaultCurrency = OnrampCountryEntity.CurrencyEmbedded(
|
||||
name = defaultCurrency.name,
|
||||
code = defaultCurrency.code,
|
||||
image = defaultCurrency.image,
|
||||
precision = defaultCurrency.precision,
|
||||
unit = defaultCurrency.unit ?: defaultCurrency.code,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.datasource.local.txhistory.db.entity.express.ExpressSyncStateE
|
|||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.OnrampCountryEntity
|
||||
|
||||
@Database(
|
||||
version = 1,
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEn
|
|||
ExpressExchangeEntity::class,
|
||||
ExpressOnrampEntity::class,
|
||||
ExpressSyncStateEntity::class,
|
||||
OnrampCountryEntity::class,
|
||||
],
|
||||
)
|
||||
abstract class TxHistoryDatabase : RoomDatabase() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.room.Query
|
|||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.OnrampCountryEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -22,12 +23,19 @@ interface ExpressHistoryDao {
|
|||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertOnramps(items: List<ExpressOnrampEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertCountries(items: List<OnrampCountryEntity>)
|
||||
|
||||
/**
|
||||
* All persisted providers keyed by [ExpressProviderEntity.id]
|
||||
*/
|
||||
@Query("SELECT * FROM express_provider")
|
||||
fun getProvidersById(): Flow<Map<@MapColumn(columnName = "id") String, ExpressProviderEntity>>
|
||||
|
||||
/** All persisted onramp countries keyed by [OnrampCountryEntity.code]. */
|
||||
@Query("SELECT * FROM onramp_country")
|
||||
fun getCountriesByCode(): Flow<Map<@MapColumn(columnName = "code") String, OnrampCountryEntity>>
|
||||
|
||||
/**
|
||||
* Outgoing swaps: the viewed currency is the swap's `from` side, so the row is stored under this
|
||||
* address ([ExpressExchangeEntity.ownerAddress] == fromAddress). Join to on-chain by `payin_hash`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity.express
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/** Persisted onramp country, matched to a transaction by [code] == [ExpressOnrampEntity.countryCode]. */
|
||||
@Entity(tableName = "onramp_country")
|
||||
data class OnrampCountryEntity(
|
||||
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "code")
|
||||
val code: String,
|
||||
|
||||
@ColumnInfo(name = "name")
|
||||
val name: String,
|
||||
|
||||
@ColumnInfo(name = "image")
|
||||
val image: String,
|
||||
|
||||
@ColumnInfo(name = "alpha3")
|
||||
val alpha3: String,
|
||||
|
||||
@ColumnInfo(name = "continent")
|
||||
val continent: String,
|
||||
|
||||
@ColumnInfo(name = "onramp_available")
|
||||
val isOnrampAvailable: Boolean,
|
||||
|
||||
@Embedded(prefix = "currency_")
|
||||
val defaultCurrency: CurrencyEmbedded,
|
||||
) {
|
||||
|
||||
data class CurrencyEmbedded(
|
||||
|
||||
@ColumnInfo(name = "name")
|
||||
val name: String,
|
||||
|
||||
@ColumnInfo(name = "code")
|
||||
val code: String,
|
||||
|
||||
@ColumnInfo(name = "image")
|
||||
val image: String?,
|
||||
|
||||
@ColumnInfo(name = "precision")
|
||||
val precision: Int,
|
||||
|
||||
@ColumnInfo(name = "unit")
|
||||
val unit: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ internal class DefaultOnrampRepository(
|
|||
override suspend fun fetchCountries(userWallet: UserWallet): List<OnrampCountry> = withContext(dispatchers.io) {
|
||||
if (!countriesStore.getSyncOrNull(COUNTRIES_KEY).isNullOrEmpty()) return@withContext emptyList()
|
||||
|
||||
val result = onrampApi.getCountries(
|
||||
val response = onrampApi.getCountries(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -123,8 +123,12 @@ internal class DefaultOnrampRepository(
|
|||
),
|
||||
)
|
||||
.getOrThrow()
|
||||
.map(countryConverter::convert)
|
||||
|
||||
if (txHistoryFeatureToggles.isNewTxHistoryEnabled) {
|
||||
expressHistoryDao.upsertCountries(response.map { it.toEntity() })
|
||||
}
|
||||
|
||||
val result = response.map(countryConverter::convert)
|
||||
countriesStore.store(COUNTRIES_KEY, result)
|
||||
|
||||
result
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ dependencies {
|
|||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.express.ExpressRepository
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.txhistory.fetcher.AppTxHistoryFetcher
|
||||
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
|
||||
import com.tangem.domain.txhistory.fetcher.WalletTxHistoryFetcher
|
||||
|
|
@ -22,6 +23,7 @@ import javax.inject.Inject
|
|||
internal class DefaultAppTxHistoryFetcher @Inject constructor(
|
||||
private val utils: TxHistoryFetcherUtils,
|
||||
private val expressRepository: ExpressRepository,
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val selectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val walletTxHistoryFetcherFactory: DefaultWalletTxHistoryFetcher.Factory,
|
||||
|
|
@ -30,9 +32,6 @@ internal class DefaultAppTxHistoryFetcher @Inject constructor(
|
|||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal val fetchers = ConcurrentHashMap<UserWalletId, WalletTxHistoryFetcher>()
|
||||
|
||||
/** Wallets whose express providers were already loaded — to load them at most once per wallet. */
|
||||
private val providersLoadedWallets = mutableSetOf<UserWalletId>()
|
||||
|
||||
init {
|
||||
defaultLaunchIn(buildFlow())
|
||||
}
|
||||
|
|
@ -53,11 +52,14 @@ internal class DefaultAppTxHistoryFetcher @Inject constructor(
|
|||
.stateIn(this)
|
||||
|
||||
walletsFlow.value.keys.createForNewWallets()
|
||||
walletsFlow.value.values.firstOrNull()?.let { wallet ->
|
||||
loadExpressProviders(wallet)
|
||||
loadOnrampCountries(wallet)
|
||||
}
|
||||
|
||||
selectedWalletUseCase.selectedFlow()
|
||||
.filter { wallet -> wallet.isMultiCurrency }
|
||||
// todo txhistory some init trigger?
|
||||
.onEach { wallet -> loadExpressProviders(wallet) }
|
||||
.launchIn(this)
|
||||
|
||||
walletsFlow
|
||||
|
|
@ -79,13 +81,17 @@ internal class DefaultAppTxHistoryFetcher @Inject constructor(
|
|||
}
|
||||
|
||||
private fun ProducerScope<*>.loadExpressProviders(wallet: UserWallet) {
|
||||
// Load once per wallet: `add` returns false if this walletId was already loaded.
|
||||
if (!providersLoadedWallets.add(wallet.walletId)) return
|
||||
flow { emit(expressRepository.getProviders(userWallet = wallet, filterProviderTypes = emptyList())) }
|
||||
.retryThreeTimes()
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun ProducerScope<*>.loadOnrampCountries(wallet: UserWallet) {
|
||||
flow { emit(onrampRepository.fetchCountries(userWallet = wallet)) }
|
||||
.retryThreeTimes()
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun Flow<Set<UserWalletId>>.createForNewWallets() = onEach { ids -> ids.createForNewWallets() }
|
||||
|
||||
private fun Set<UserWalletId>.createForNewWallets() = this.forEach { walletId -> getOrPutFetcher(walletId) }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.common.converter.ExpressProviderConverter
|
|||
import com.tangem.data.txhistory.repository.converter.ExpressStatusMapper
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressOnrampConverter
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressSwapConverter
|
||||
import com.tangem.data.txhistory.repository.converter.OnrampCountryConverter
|
||||
import com.tangem.data.txhistory.repository.factory.ExpressTransactionAssetFactory
|
||||
import com.tangem.data.txhistory.repository.factory.toAssetId
|
||||
import com.tangem.data.txhistory.repository.paging.TxHistoryPageBatchFetcher
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
|||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.OnrampCountryEntity
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
|
|
@ -50,6 +52,7 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
private val expressProviderConverter = ExpressProviderConverter()
|
||||
private val swapConverter = ExpressSwapConverter()
|
||||
private val onrampConverter = ExpressOnrampConverter()
|
||||
private val onrampCountryConverter = OnrampCountryConverter()
|
||||
private val TxHistoryListConfig.storeKey get() = TxHistoryItemsStore.Key(userWalletId, currency)
|
||||
|
||||
override fun getExpressHistory(
|
||||
|
|
@ -90,35 +93,46 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
activeStatuses = ExpressStatusMapper.activeOnrampStatuses,
|
||||
).distinctUntilChanged(),
|
||||
flow4 = expressHistoryDao.getProvidersById().distinctUntilChanged(),
|
||||
transform = { outgoingSwaps, incomingSwaps, onramps, providers ->
|
||||
flow5 = expressHistoryDao.getCountriesByCode().distinctUntilChanged(),
|
||||
transform = { outgoingSwaps, incomingSwaps, onramps, providers, countries ->
|
||||
buildExpressHistory(
|
||||
userWalletId = userWalletId,
|
||||
sources = ExpressHistorySources(
|
||||
outgoingSwaps = outgoingSwaps,
|
||||
incomingSwaps = incomingSwaps,
|
||||
onramps = onramps,
|
||||
providers = providers,
|
||||
countries = countries,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
emitAll(flow)
|
||||
}.flowOn(dispatchers.io)
|
||||
|
||||
/** The reactive express-history inputs gathered from the DB in a single [combine] tick. */
|
||||
private data class ExpressHistorySources(
|
||||
val outgoingSwaps: List<ExpressExchangeEntity>,
|
||||
val incomingSwaps: List<ExpressExchangeEntity>,
|
||||
val onramps: List<ExpressOnrampEntity>,
|
||||
val providers: Map<String, ExpressProviderEntity>,
|
||||
val countries: Map<String, OnrampCountryEntity>,
|
||||
)
|
||||
|
||||
private suspend fun buildExpressHistory(
|
||||
userWalletId: UserWalletId,
|
||||
outgoingSwaps: List<ExpressExchangeEntity>,
|
||||
incomingSwaps: List<ExpressExchangeEntity>,
|
||||
onramps: List<ExpressOnrampEntity>,
|
||||
providers: Map<String, ExpressProviderEntity>,
|
||||
sources: ExpressHistorySources,
|
||||
): List<ExpressTx> {
|
||||
val currencies = expressTransactionAssetFactory.create(
|
||||
userWalletId = userWalletId,
|
||||
outgoingSwaps = outgoingSwaps,
|
||||
incomingSwaps = incomingSwaps,
|
||||
onramps = onramps,
|
||||
outgoingSwaps = sources.outgoingSwaps,
|
||||
incomingSwaps = sources.incomingSwaps,
|
||||
onramps = sources.onramps,
|
||||
)
|
||||
fun String.expressProvider() = providers[this]?.let(expressProviderConverter::convert)
|
||||
fun String.expressProvider() = sources.providers[this]?.let(expressProviderConverter::convert)
|
||||
fun String.onrampCountry() = sources.countries[this]?.let(onrampCountryConverter::convert)
|
||||
return buildList {
|
||||
outgoingSwaps.forEach { entity ->
|
||||
sources.outgoingSwaps.forEach { entity ->
|
||||
val input = ExpressSwapConverter.Input(
|
||||
entity = entity,
|
||||
provider = entity.providerId.expressProvider(),
|
||||
|
|
@ -128,7 +142,7 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
)
|
||||
add(swapConverter.convert(input))
|
||||
}
|
||||
incomingSwaps.forEach { entity ->
|
||||
sources.incomingSwaps.forEach { entity ->
|
||||
val input = ExpressSwapConverter.Input(
|
||||
entity = entity,
|
||||
provider = entity.providerId.expressProvider(),
|
||||
|
|
@ -138,11 +152,12 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
)
|
||||
add(swapConverter.convert(input))
|
||||
}
|
||||
onramps.forEach { entity ->
|
||||
sources.onramps.forEach { entity ->
|
||||
val input = ExpressOnrampConverter.Input(
|
||||
entity = entity,
|
||||
provider = entity.providerId.expressProvider(),
|
||||
toCurrency = currencies[entity.to.toAssetId()],
|
||||
country = entity.countryCode.onrampCountry(),
|
||||
)
|
||||
add(onrampConverter.convert(input))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.express.models.ExpressProvider
|
|||
import com.tangem.domain.express.models.ExpressTransactionAsset
|
||||
import com.tangem.domain.express.models.OnrampTransaction
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.domain.txhistory.model.ExpressTx
|
||||
|
|
@ -64,6 +65,7 @@ internal class ExpressOnrampConverter : Converter<ExpressOnrampConverter.Input,
|
|||
decimals = entity.to.decimals,
|
||||
cryptoCurrency = value.toCurrency,
|
||||
),
|
||||
country = value.country,
|
||||
),
|
||||
txInfo = null,
|
||||
)
|
||||
|
|
@ -73,6 +75,7 @@ internal class ExpressOnrampConverter : Converter<ExpressOnrampConverter.Input,
|
|||
val entity: ExpressOnrampEntity,
|
||||
val provider: ExpressProvider?,
|
||||
val toCurrency: CryptoCurrency? = null,
|
||||
val country: OnrampCountry? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.data.txhistory.repository.converter
|
||||
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.OnrampCountryEntity
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/** Maps a persisted [OnrampCountryEntity] into the domain [OnrampCountry]. */
|
||||
internal class OnrampCountryConverter : Converter<OnrampCountryEntity, OnrampCountry> {
|
||||
|
||||
override fun convert(value: OnrampCountryEntity): OnrampCountry {
|
||||
return OnrampCountry(
|
||||
id = "${value.alpha3}-${value.name}",
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
alpha3 = value.alpha3,
|
||||
continent = value.continent,
|
||||
defaultCurrency = OnrampCurrency(
|
||||
name = value.defaultCurrency.name,
|
||||
code = value.defaultCurrency.code,
|
||||
image = value.defaultCurrency.image,
|
||||
precision = value.defaultCurrency.precision,
|
||||
unit = value.defaultCurrency.unit,
|
||||
),
|
||||
onrampAvailable = value.isOnrampAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.express.ExpressRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
|
|
@ -14,7 +15,6 @@ import io.mockk.*
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
|
@ -29,14 +29,16 @@ internal class DefaultAppTxHistoryFetcherTest {
|
|||
private val selectedWalletUseCase: GetSelectedWalletUseCase = mockk()
|
||||
private val walletFetcherFactory: DefaultWalletTxHistoryFetcher.Factory = mockk()
|
||||
private val expressRepository: ExpressRepository = mockk()
|
||||
private val onrampRepository: OnrampRepository = mockk()
|
||||
|
||||
private val currency: CryptoCurrency = MockCryptoCurrencyFactory().ethereum
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(getWalletsUseCase, selectedWalletUseCase, walletFetcherFactory, expressRepository)
|
||||
clearMocks(getWalletsUseCase, selectedWalletUseCase, walletFetcherFactory, expressRepository, onrampRepository)
|
||||
every { selectedWalletUseCase.selectedFlow() } returns emptyFlow()
|
||||
coEvery { expressRepository.getProviders(any(), any()) } returns emptyList()
|
||||
coEvery { onrampRepository.fetchCountries(any()) } returns emptyList()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -147,15 +149,16 @@ internal class DefaultAppTxHistoryFetcherTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `loads express providers when selected wallet is multi-currency`() = runTest {
|
||||
fun `loads express providers and onramp countries for the first wallet on init`() = runTest {
|
||||
// Arrange
|
||||
val utils = createUtils()
|
||||
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns MutableStateFlow(linkedMapOf())
|
||||
val wallet = mockk<UserWallet.Cold>(relaxed = true) {
|
||||
every { isMultiCurrency } returns true
|
||||
every { walletId } returns WALLET_ID_1
|
||||
}
|
||||
every { selectedWalletUseCase.selectedFlow() } returns flowOf(wallet)
|
||||
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns
|
||||
MutableStateFlow(linkedMapOf(WALLET_ID_1 to wallet))
|
||||
every { walletFetcherFactory.create(WALLET_ID_1) } returns relaxedWalletFetcher()
|
||||
|
||||
// Act
|
||||
createFetcher(utils)
|
||||
|
|
@ -163,48 +166,40 @@ internal class DefaultAppTxHistoryFetcherTest {
|
|||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { expressRepository.getProviders(wallet, emptyList()) }
|
||||
coVerify(exactly = 1) { onrampRepository.fetchCountries(wallet) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loads express providers only once per wallet`() = runTest {
|
||||
fun `does not load express data when there are no wallets`() = runTest {
|
||||
// Arrange
|
||||
val utils = createUtils()
|
||||
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns MutableStateFlow(linkedMapOf())
|
||||
val wallet = mockk<UserWallet.Cold>(relaxed = true) {
|
||||
every { isMultiCurrency } returns true
|
||||
every { walletId } returns WALLET_ID_1
|
||||
}
|
||||
// Same wallet selected several times.
|
||||
every { selectedWalletUseCase.selectedFlow() } returns flowOf(wallet, wallet, wallet)
|
||||
|
||||
// Act
|
||||
createFetcher(utils)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { expressRepository.getProviders(wallet, emptyList()) }
|
||||
coVerify(inverse = true) { expressRepository.getProviders(any(), any()) }
|
||||
coVerify(inverse = true) { onrampRepository.fetchCountries(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provider loading failure does not break the wallet pipeline`() = runTest {
|
||||
// Arrange
|
||||
val utils = createUtils()
|
||||
val walletsFlow = MutableStateFlow(linkedMapOf<UserWalletId, UserWallet>())
|
||||
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
|
||||
val wallet = mockk<UserWallet.Cold>(relaxed = true) {
|
||||
every { isMultiCurrency } returns true
|
||||
every { walletId } returns WALLET_ID_1
|
||||
}
|
||||
every { selectedWalletUseCase.selectedFlow() } returns flowOf(wallet)
|
||||
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns
|
||||
MutableStateFlow(linkedMapOf(WALLET_ID_1 to wallet))
|
||||
coEvery { expressRepository.getProviders(any(), any()) } throws RuntimeException("boom")
|
||||
val walletFetcher1 = relaxedWalletFetcher()
|
||||
every { walletFetcherFactory.create(WALLET_ID_1) } returns walletFetcher1
|
||||
|
||||
val fetcher = createFetcher(utils)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Act — the provider error is swallowed, so the wallet pipeline must keep working.
|
||||
walletsFlow.value = linkedMapOf(WALLET_ID_1 to mockk())
|
||||
val fetcher = createFetcher(utils)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
|
|
@ -220,6 +215,7 @@ internal class DefaultAppTxHistoryFetcherTest {
|
|||
private fun createFetcher(utils: DefaultTxHistoryFetcherUtils) = DefaultAppTxHistoryFetcher(
|
||||
utils = utils,
|
||||
expressRepository = expressRepository,
|
||||
onrampRepository = onrampRepository,
|
||||
getWalletsUseCase = getWalletsUseCase,
|
||||
selectedWalletUseCase = selectedWalletUseCase,
|
||||
walletTxHistoryFetcherFactory = walletFetcherFactory,
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ dependencies {
|
|||
implementation(deps.kotlin.serialization)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.onramp.models)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.express.models
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ import com.tangem.domain.tokens.model.AmountType
|
|||
* @property payoutHash On-chain hash of the payout (received) leg, if known.
|
||||
* @property fromFiat The fiat paid.
|
||||
* @property toAsset The crypto asset received.
|
||||
* @property country The country the onramp was made from; `null` if not resolved.
|
||||
*/
|
||||
data class OnrampTransaction(
|
||||
val txId: String,
|
||||
|
|
@ -25,4 +27,5 @@ data class OnrampTransaction(
|
|||
/** The [Amount.type] is [AmountType.FiatType] . */
|
||||
val fromFiat: Amount,
|
||||
val toAsset: ExpressTransactionAsset,
|
||||
val country: OnrampCountry? = null,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue