diff --git a/core/datasource/schemas/com.tangem.datasource.local.txhistory.db.TxHistoryDatabase/1.json b/core/datasource/schemas/com.tangem.datasource.local.txhistory.db.TxHistoryDatabase/1.json index 41c9ded541..cfd76c709e 100644 --- a/core/datasource/schemas/com.tangem.datasource.local.txhistory.db.TxHistoryDatabase/1.json +++ b/core/datasource/schemas/com.tangem.datasource.local.txhistory.db.TxHistoryDatabase/1.json @@ -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')" ] } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/converter/OnrampCountryEntityConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/converter/OnrampCountryEntityConverter.kt new file mode 100644 index 0000000000..56ec2a01f8 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/converter/OnrampCountryEntityConverter.kt @@ -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, + ), + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/TxHistoryDatabase.kt b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/TxHistoryDatabase.kt index 549e980d64..331c2ce3e7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/TxHistoryDatabase.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/TxHistoryDatabase.kt @@ -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() { diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/dao/ExpressHistoryDao.kt b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/dao/ExpressHistoryDao.kt index 802598fc3d..9736bc2b17 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/dao/ExpressHistoryDao.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/dao/ExpressHistoryDao.kt @@ -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) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertCountries(items: List) + /** * All persisted providers keyed by [ExpressProviderEntity.id] */ @Query("SELECT * FROM express_provider") fun getProvidersById(): Flow> + /** All persisted onramp countries keyed by [OnrampCountryEntity.code]. */ + @Query("SELECT * FROM onramp_country") + fun getCountriesByCode(): Flow> + /** * 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`. diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/entity/express/OnrampCountryEntity.kt b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/entity/express/OnrampCountryEntity.kt new file mode 100644 index 0000000000..5ee6ac7ddd --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/txhistory/db/entity/express/OnrampCountryEntity.kt @@ -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, + ) +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index e783308ca6..6d69d7ab7b 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -115,7 +115,7 @@ internal class DefaultOnrampRepository( override suspend fun fetchCountries(userWallet: UserWallet): List = 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 diff --git a/data/txhistory/build.gradle.kts b/data/txhistory/build.gradle.kts index 0320d9912c..54c7e3792f 100644 --- a/data/txhistory/build.gradle.kts +++ b/data/txhistory/build.gradle.kts @@ -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) diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcher.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcher.kt index c4e4e208a3..3141aa5a00 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcher.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcher.kt @@ -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() - /** Wallets whose express providers were already loaded — to load them at most once per wallet. */ - private val providersLoadedWallets = mutableSetOf() - 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>.createForNewWallets() = onEach { ids -> ids.createForNewWallets() } private fun Set.createForNewWallets() = this.forEach { walletId -> getOrPutFetcher(walletId) } diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt index 704a77c58c..4035351559 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/RefactoredTxHistoryRepository.kt @@ -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, - outgoingSwaps = outgoingSwaps, - incomingSwaps = incomingSwaps, - onramps = onramps, - providers = providers, + 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, + val incomingSwaps: List, + val onramps: List, + val providers: Map, + val countries: Map, + ) + private suspend fun buildExpressHistory( userWalletId: UserWalletId, - outgoingSwaps: List, - incomingSwaps: List, - onramps: List, - providers: Map, + sources: ExpressHistorySources, ): List { 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)) } diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt index 5f07b6dc5a..8e30176f4a 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt @@ -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 { + + 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, + ) + } +} \ No newline at end of file diff --git a/data/txhistory/src/test/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcherTest.kt b/data/txhistory/src/test/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcherTest.kt index 886dbb1c83..45ce663c52 100644 --- a/data/txhistory/src/test/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcherTest.kt +++ b/data/txhistory/src/test/kotlin/com/tangem/data/txhistory/fetcher/DefaultAppTxHistoryFetcherTest.kt @@ -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(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(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()) - every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow val wallet = mockk(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, diff --git a/domain/express/models/build.gradle.kts b/domain/express/models/build.gradle.kts index a63174eff0..0ebcc0b441 100644 --- a/domain/express/models/build.gradle.kts +++ b/domain/express/models/build.gradle.kts @@ -9,4 +9,5 @@ dependencies { implementation(deps.kotlin.serialization) implementation(projects.domain.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.onramp.models) } \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/OnrampTransaction.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/OnrampTransaction.kt index 4940b3d055..e6388dee75 100644 --- a/domain/express/models/src/main/java/com/tangem/domain/express/models/OnrampTransaction.kt +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/OnrampTransaction.kt @@ -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, ) \ No newline at end of file