Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-11 17:45:54 +03:00
parent a2e602f1f1
commit db0c6cc933
11 changed files with 252 additions and 83 deletions

View file

@ -14,10 +14,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.*
class GetTokenListUseCase(
internal val currenciesRepository: CurrenciesRepository,
@ -27,8 +24,8 @@ class GetTokenListUseCase(
) {
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow<Either<TokenListError, TokenList>> {
return getTokensStatuses(userWalletId, refresh).flatMapMerge flatMap@{ maybeTokens ->
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Flow<Either<TokenListError, TokenList>> {
return getTokensStatuses(userWalletId, refresh).flatMapMerge { maybeTokens ->
maybeTokens.fold(
ifLeft = { error ->
flowOf(error.left())

View file

@ -86,4 +86,15 @@ data class CryptoCurrencyStatus(
override val priceChange: BigDecimal?,
override val hasTransactionsInProgress: Boolean,
) : Status()
/**
* Represents a state where the token is available, but there is no current quote available for it.
*
* @property amount The amount of the token.
* @property hasTransactionsInProgress Indicates if there are any transactions in progress related to the token.
*/
data class NoQuote(
override val amount: BigDecimal,
override val hasTransactionsInProgress: Boolean,
) : Status()
}

View file

@ -3,7 +3,8 @@ package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.*
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.models.Quote
@ -36,25 +37,41 @@ internal class CurrenciesStatusesOperations(
@OptIn(ExperimentalCoroutinesApi::class)
fun getCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return getMultiCurrencyWalletCurrencies().flatMapMerge flatMap@{ maybeCurrencies ->
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
val nonEmptyCurrencies = maybeCurrencies.fold(
ifLeft = { error ->
return@flatMap flowOf(error.left())
emit(error.left())
return@transformLatest
},
ifRight = { it.toNonEmptyListOrNull() },
) ?: return@flatMap flowOf(emptyList<CryptoCurrencyStatus>().right())
ifRight = List<CryptoCurrency>::toNonEmptyListOrNull,
)
if (nonEmptyCurrencies == null) {
val emptyCurrenciesStatuses = emptyList<CryptoCurrencyStatus>()
emit(emptyCurrenciesStatuses.right())
return@transformLatest
} else if (!refresh) {
val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
maybeNetworkStatuses = null,
maybeQuotes = null,
)
emit(maybeLoadingCurrenciesStatuses)
}
val (networksIds, currenciesIds) = getIds(nonEmptyCurrencies)
combine(
val currenciesFlow = combine(
getQuotes(currenciesIds),
getNetworksStatuses(networksIds),
) { maybeQuotes, maybeNetworksStatuses ->
either {
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes.bind(), maybeNetworksStatuses.bind())
}
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
}
}
emitAll(currenciesFlow)
}.conflate()
}
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<Either<Error, CryptoCurrencyStatus>> {
@ -76,14 +93,16 @@ internal class CurrenciesStatusesOperations(
}
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<Either<Error, CryptoCurrencyStatus>> {
val quoteFlow = getQuotes(nonEmptySetOf(currency.id))
val (networksIds, currenciesIds) = getIds(nonEmptyListOf(currency))
val quoteFlow = getQuotes(currenciesIds)
.map { maybeQuotes ->
maybeQuotes.map { quotes ->
quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
}
}
val statusFlow = getNetworksStatuses(nonEmptySetOf(currency.networkId))
val statusFlow = getNetworksStatuses(networksIds)
.map { maybeStatuses ->
maybeStatuses.map { statuses ->
statuses.singleOrNull { it.networkId == currency.networkId }
@ -91,34 +110,58 @@ internal class CurrenciesStatusesOperations(
}
return combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus ->
either {
createStatus(currency, maybeQuote.bind(), maybeNetworkStatus.bind())
}
createStatus(currency, maybeQuote, maybeNetworkStatus)
}
}
private fun createCurrenciesStatuses(
currencies: NonEmptyList<CryptoCurrency>,
quotes: Set<Quote>,
networkStatuses: Set<NetworkStatus>,
): List<CryptoCurrencyStatus> {
return currencies.map { currency ->
val quote = quotes.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networkStatuses.firstOrNull { it.networkId == currency.networkId }
maybeQuotes: Either<Error, Set<Quote>>?,
maybeNetworkStatuses: Either<Error, Set<NetworkStatus>>?,
): Either<Error, List<CryptoCurrencyStatus>> = either {
var quotesRetrievingFailed = false
createStatus(currency, quote, networkStatus)
val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull()
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
quotesRetrievingFailed = true
null
}
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.networkId == currency.networkId }
createStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed)
}
}
private fun createStatus(
token: CryptoCurrency,
currency: CryptoCurrency,
maybeQuote: Either<Error, Quote?>,
maybeNetworkStatus: Either<Error, NetworkStatus?>,
): Either<Error, CryptoCurrencyStatus> = either {
var quoteRetrievingFailed = false
val networkStatus = maybeNetworkStatus.bind()
val quote = recover({ maybeQuote.bind() }) {
quoteRetrievingFailed = true
null
}
createStatus(currency, quote, networkStatus, ignoreQuote = quoteRetrievingFailed)
}
private fun createStatus(
currency: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
ignoreQuote: Boolean,
): CryptoCurrencyStatus {
val currencyStatusOperations = CurrencyStatusOperations(
currency = token,
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = ignoreQuote,
)
return currencyStatusOperations.createTokenStatus()

View file

@ -10,6 +10,7 @@ internal class CurrencyStatusOperations(
private val currency: CryptoCurrency,
private val quote: Quote?,
private val networkStatus: NetworkStatus?,
private val ignoreQuote: Boolean,
) {
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
@ -28,6 +29,10 @@ internal class CurrencyStatusOperations(
val amount = status.amounts[currency.id] ?: return CryptoCurrencyStatus.Unreachable
return when {
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
amount = amount,
hasTransactionsInProgress = status.hasTransactionsInProgress,
)
currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom(
amount = amount,
fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate),

View file

@ -27,7 +27,7 @@ internal class TokenListFiatBalanceOperations(
break
}
is CryptoCurrencyStatus.NoAccount -> {
fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance)
fiatBalance = recalculateBalanceWithoutQuote(fiatBalance)
}
is CryptoCurrencyStatus.Loaded -> {
fiatBalance = recalculateBalance(status, fiatBalance)
@ -35,13 +35,16 @@ internal class TokenListFiatBalanceOperations(
is CryptoCurrencyStatus.Custom -> {
fiatBalance = recalculateBalance(status, fiatBalance)
}
is CryptoCurrencyStatus.NoQuote -> {
fiatBalance = recalculateBalanceWithoutQuote(fiatBalance)
}
}
}
return fiatBalance
}
private fun recalculateBalanceForNoAccountStatus(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance {
private fun recalculateBalanceWithoutQuote(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance {
return with(currentBalance) {
(this as? TokenList.FiatBalance.Loaded)?.copy(
isAllAmountsSummarized = false,

View file

@ -59,9 +59,9 @@ internal class GetPrimaryCurrencyUseCaseTest {
}
@Test
fun `when quotes getting failed then error should be received`() = runTest {
fun `when quotes getting failed then currency with no quote status should be received`() = runTest {
// Given
val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left()
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
@ -100,8 +100,8 @@ internal class GetPrimaryCurrencyUseCaseTest {
}
@Test
fun `when quotes flow is empty then error should be received`() = runTest {
val expectedResult = CurrencyError.UnableToCreateCurrency.left()
fun `when quotes flow is empty then no quote status should be received`() = runTest {
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
val useCase = getUseCase(quotes = flowOf())

View file

@ -10,6 +10,7 @@ import com.tangem.domain.tokens.mock.MockQuotes
import com.tangem.domain.tokens.mock.MockTokenLists
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.models.Quote
@ -32,7 +33,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when list ungrouped and unsorted then correct token list should be returned`() = runTest {
// Given
val expectedResult = MockTokenLists.failedUngroupedTokenList.right()
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.right(),
MockTokenLists.failedUngroupedTokenList.right(),
)
val useCase = getUseCase(
isGrouped = flowOf(false.right()),
@ -40,7 +44,30 @@ internal class GetTokenListUseCaseTest {
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
}
@Test
fun `when list refreshed then correct token list should be returned`() = runTest {
// Given
val expectedResult = listOf(
MockTokenLists.failedUngroupedTokenList.right(),
)
val useCase = getUseCase(
isGrouped = flowOf(false.right()),
isSortedByBalance = flowOf(false.right()),
)
// When
val result = useCase(userWalletId, refresh = true)
.take(count = 1)
.toList()
// Then
assertEquals(expectedResult, result)
@ -61,14 +88,22 @@ internal class GetTokenListUseCaseTest {
}
@Test
fun `when quotes getting failed then error should be received`() = runTest {
fun `when quotes getting failed then token list without quotes should be received`() = runTest {
// Given
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.right(),
MockTokenLists.noQuotesUngroupedTokenList.right(),
)
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
val useCase = getUseCase(
quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()),
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -99,7 +134,7 @@ internal class GetTokenListUseCaseTest {
val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId, refresh = true).first()
// Then
assertEquals(expectedResult, result)
@ -138,6 +173,7 @@ internal class GetTokenListUseCaseTest {
// Given
val error = DataError.NetworkError.NoInternetConnection.left()
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.right(),
MockTokenLists.failedUngroupedTokenList.right(),
TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left(),
)
@ -151,7 +187,7 @@ internal class GetTokenListUseCaseTest {
// When
val result = useCase(userWalletId)
.take(count = 2)
.take(count = 3)
.toList()
// Then
@ -160,12 +196,17 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when list grouped then correct token list should be received`() = runTest {
val expectedResult = MockTokenLists.failedGroupedTokenList.right()
val expectedResult = listOf(
MockTokenLists.loadingGroupedTokenList.right(),
MockTokenLists.failedGroupedTokenList.right(),
)
val useCase = getUseCase(isGrouped = flowOf(true.right()))
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -189,7 +230,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when list is sorted and ungrouped then correct token list should be received`() = runTest {
val expectedResult = MockTokenLists.sortedUngroupedTokenList.right()
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(),
MockTokenLists.sortedUngroupedTokenList.right(),
)
val useCase = getUseCase(
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
@ -198,7 +242,9 @@ internal class GetTokenListUseCaseTest {
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -206,7 +252,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when list is sorted and grouped then correct token list should be received`() = runTest {
val expectedResult = MockTokenLists.sortedGroupedTokenList.right()
val expectedResult = listOf(
MockTokenLists.loadingGroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(),
MockTokenLists.sortedGroupedTokenList.right(),
)
val useCase = getUseCase(
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
@ -215,7 +264,9 @@ internal class GetTokenListUseCaseTest {
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -236,7 +287,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when networks is empty and list is grouped then ungrouped list should be received`() = runTest {
val expectedResult = TokenListError.UnableToSortTokenList(MockTokenLists.failedUngroupedTokenList).left()
val expectedResult = listOf(
TokenListError.UnableToSortTokenList(MockTokenLists.loadingUngroupedTokenList).left(),
TokenListError.UnableToSortTokenList(MockTokenLists.failedUngroupedTokenList).left(),
)
val useCase = getUseCase(
networks = emptySet<Network>().right(),
@ -244,7 +298,9 @@ internal class GetTokenListUseCaseTest {
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -265,12 +321,17 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when networks statuses flow is empty then error should be received`() = runTest {
val expectedResult = TokenListError.EmptyTokens.left()
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.right(),
TokenListError.EmptyTokens.left(),
)
val useCase = getUseCase(statuses = flowOf())
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)
@ -290,13 +351,21 @@ internal class GetTokenListUseCaseTest {
}
@Test
fun `when quotes flow is empty then error should be received`() = runTest {
val expectedResult = TokenListError.EmptyTokens.left()
fun `when quotes flow is empty then list without quotes should be received`() = runTest {
val expectedResult = listOf(
MockTokenLists.loadingUngroupedTokenList.right(),
MockTokenLists.noQuotesUngroupedTokenList.right(),
)
val useCase = getUseCase(quotes = flowOf())
val useCase = getUseCase(
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
quotes = flowOf(),
)
// When
val result = useCase(userWalletId).first()
val result = useCase(userWalletId)
.take(count = 2)
.toList()
// Then
assertEquals(expectedResult, result)

View file

@ -41,9 +41,14 @@ internal object MockTokenLists {
sortedBy = TokenList.SortType.NONE,
)
val noQuotesUngroupedTokenList = failedUngroupedTokenList.copy(
totalFiatBalance = TokenList.FiatBalance.Loaded(amount = BigDecimal.ZERO, isAllAmountsSummarized = false),
currencies = MockTokensStates.noQuotesTokensStatuses,
)
val loadingUngroupedTokenList = with(failedUngroupedTokenList) {
copy(
currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) }.toNonEmptyListOrNull()!!,
currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) },
totalFiatBalance = TokenList.FiatBalance.Loading,
)
}
@ -54,8 +59,7 @@ internal object MockTokenLists {
groups = groups.map { group ->
group.copy(
currencies = group.currencies
.map { it.copy(value = CryptoCurrencyStatus.Loading) }
.toNonEmptyListOrNull()!!,
.map { it.copy(value = CryptoCurrencyStatus.Loading) },
)
}.toNonEmptyListOrNull()!!,
)
@ -95,7 +99,6 @@ internal object MockTokenLists {
get() {
val tokens = MockTokensStates.loadedTokensStates
.sortedByDescending { it.value.fiatAmount }
.toNonEmptyListOrNull()!!
return unsortedUngroupedTokenList.copy(
currencies = tokens,

View file

@ -87,4 +87,13 @@ internal object MockTokensStates {
),
)
}
val noQuotesTokensStatuses = loadedTokensStates.map { currency ->
currency.copy(
value = CryptoCurrencyStatus.NoQuote(
amount = currency.value.amount!!,
hasTransactionsInProgress = false,
),
)
}
}

View file

@ -43,23 +43,14 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
private fun getMarketPriceState(status: CryptoCurrencyStatus.Status, currencyName: String): MarketPriceBlockState {
return when (status) {
is CryptoCurrencyStatus.Loaded -> MarketPriceBlockState.Content(
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.Loaded,
-> MarketPriceBlockState.Content(
currencyName = currencyName,
price = BigDecimalFormatter.formatFiatAmount(
fiatAmount = status.fiatRate,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
),
price = formatPrice(status),
priceChangeConfig = PriceChangeConfig(
valueInPercent = BigDecimalFormatter.formatPercent(
percent = status.priceChange,
useAbsoluteValue = true,
),
type = if (status.priceChange > BigDecimal.ZERO) {
PriceChangeConfig.Type.UP
} else {
PriceChangeConfig.Type.DOWN
},
valueInPercent = formatPriceChange(status),
type = getPriceChangeType(status),
),
)
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencyName)
@ -77,7 +68,9 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
): WalletsListConfig {
val selectedWallet = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex]
val updatedWallet = when (status) {
is CryptoCurrencyStatus.Loaded -> {
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.Loaded,
-> {
WalletCardState.Content(
id = selectedWallet.id,
title = selectedWallet.title,
@ -88,11 +81,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
),
imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick,
balance = BigDecimalFormatter.formatFiatAmount(
fiatAmount = status.fiatAmount,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
),
balance = formatFiatAmount(status),
)
}
is CryptoCurrencyStatus.Loading -> {
@ -124,4 +113,43 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
.set(index = state.walletsListConfig.selectedWalletIndex, element = updatedWallet),
)
}
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeConfig.Type {
val priceChange = status.priceChange ?: return PriceChangeConfig.Type.DOWN
return if (priceChange > BigDecimal.ZERO) {
PriceChangeConfig.Type.UP
} else {
PriceChangeConfig.Type.DOWN
}
}
private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String {
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
return BigDecimalFormatter.formatPercent(
percent = priceChange,
useAbsoluteValue = true,
)
}
private fun formatPrice(status: CryptoCurrencyStatus.Status): String {
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatRate,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
)
}
private fun formatFiatAmount(status: CryptoCurrencyStatus.Status): String {
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = fiatAmount,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
)
}
}

View file

@ -33,6 +33,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
is CryptoCurrencyStatus.Loading -> TokenItemState.Loading
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
-> value.mapToTokenItemState()
// TODO: Add other token item states, currently not designed
is CryptoCurrencyStatus.MissedDerivation,