Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-24 17:32:48 +04:00
parent e7b7c086bb
commit ef8a13cfaa
14 changed files with 267 additions and 46 deletions

View file

@ -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')"
]
}
}

View file

@ -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,
),
)
}

View file

@ -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() {

View file

@ -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`.

View file

@ -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,
)
}

View file

@ -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

View file

@ -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)

View file

@ -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) }

View file

@ -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))
}

View file

@ -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,
)
}

View file

@ -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,
)
}
}

View file

@ -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,

View file

@ -9,4 +9,5 @@ dependencies {
implementation(deps.kotlin.serialization)
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.onramp.models)
}

View file

@ -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,
)