Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-01 13:56:39 +03:00
parent 642bcca31f
commit 81a16c8bb1
46 changed files with 610 additions and 615 deletions

View file

@ -7,61 +7,61 @@ import java.math.BigDecimal
internal object MockQuotes {
val quote1 = Quote(
tokenId = MockTokens.token1.id,
currencyId = MockTokens.token1.id,
fiatRate = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"),
)
val quote2 = Quote(
tokenId = MockTokens.token2.id,
currencyId = MockTokens.token2.id,
fiatRate = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"),
)
val quote3 = Quote(
tokenId = MockTokens.token3.id,
currencyId = MockTokens.token3.id,
fiatRate = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"),
)
val quote4 = Quote(
tokenId = MockTokens.token4.id,
currencyId = MockTokens.token4.id,
fiatRate = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"),
)
val quote5 = Quote(
tokenId = MockTokens.token5.id,
currencyId = MockTokens.token5.id,
fiatRate = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"),
)
val quote6 = Quote(
tokenId = MockTokens.token6.id,
currencyId = MockTokens.token6.id,
fiatRate = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"),
)
val quote7 = Quote(
tokenId = MockTokens.token7.id,
currencyId = MockTokens.token7.id,
fiatRate = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"),
)
val quote8 = Quote(
tokenId = MockTokens.token8.id,
currencyId = MockTokens.token8.id,
fiatRate = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"),
)
val quote9 = Quote(
tokenId = MockTokens.token9.id,
currencyId = MockTokens.token9.id,
fiatRate = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"),
)
val quote10 = Quote(
tokenId = MockTokens.token10.id,
currencyId = MockTokens.token10.id,
fiatRate = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"),
)

View file

@ -1,25 +1,23 @@
package com.tangem.data.tokens.mock
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal object MockTokens {
val token1
get() = Token(
id = Token.ID("token1"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token1"),
networkId = MockNetworks.network1.id,
name = "Token 1",
symbol = "T1",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token2
get() = Token(
id = Token.ID("token2"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token2"),
networkId = MockNetworks.network1.id,
name = "Token 2",
symbol = "T2",
@ -30,8 +28,8 @@ internal object MockTokens {
derivationPath = null,
)
val token3
get() = Token(
id = Token.ID("token3"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token3"),
networkId = MockNetworks.network1.id,
name = "Token 3",
symbol = "T3",
@ -42,20 +40,18 @@ internal object MockTokens {
derivationPath = null,
)
val token4
get() = Token(
id = Token.ID("token4"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token4"),
networkId = MockNetworks.network2.id,
name = "Token 4",
symbol = "T4",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token5
get() = Token(
id = Token.ID("token5"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token5"),
networkId = MockNetworks.network2.id,
name = "Token 5",
symbol = "T5",
@ -66,8 +62,8 @@ internal object MockTokens {
derivationPath = null,
)
val token6
get() = Token(
id = Token.ID("token6"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token6"),
networkId = MockNetworks.network2.id,
name = "Token 6",
symbol = "T6",
@ -78,20 +74,18 @@ internal object MockTokens {
derivationPath = null,
)
val token7
get() = Token(
id = Token.ID("token7"),
get() = CryptoCurrency.Coin(
id = CryptoCurrency.ID("token7"),
networkId = MockNetworks.network3.id,
name = "Token 7",
symbol = "T7",
isCustom = false,
decimals = 8,
iconUrl = null,
contractAddress = null,
derivationPath = null,
)
val token8
get() = Token(
id = Token.ID("token8"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token8"),
networkId = MockNetworks.network3.id,
name = "Token 8",
symbol = "T8",
@ -102,8 +96,8 @@ internal object MockTokens {
derivationPath = null,
)
val token9
get() = Token(
id = Token.ID("token9"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token9"),
networkId = MockNetworks.network3.id,
name = "Token 9",
symbol = "T9",
@ -114,8 +108,8 @@ internal object MockTokens {
derivationPath = null,
)
val token10
get() = Token(
id = Token.ID("token10"),
get() = CryptoCurrency.Token(
id = CryptoCurrency.ID("token10"),
networkId = MockNetworks.network3.id,
name = "Token 10",
symbol = "T10",

View file

@ -1,15 +1,15 @@
package com.tangem.data.tokens.repository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.CardTokensFactory
import com.tangem.data.tokens.utils.ResponseTokensFactory
import com.tangem.data.tokens.utils.CardCurrenciesFactory
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
import com.tangem.data.tokens.utils.UserTokensResponseFactory
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
@ -27,18 +27,18 @@ internal class DefaultTokensRepository(
) : TokensRepository {
private val demoConfig = DemoConfig()
private val responseTokensFactory = ResponseTokensFactory(demoConfig)
private val cardTokensFactory = CardTokensFactory(demoConfig)
private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig)
private val cardCurrenciesFactory = CardCurrenciesFactory(demoConfig)
private val userTokensResponseFactory = UserTokensResponseFactory()
override suspend fun saveTokens(
userWalletId: UserWalletId,
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
) = withContext(dispatchers.io) {
val response = userTokensResponseFactory.createUserTokensResponse(
tokens = tokens,
currencies = currencies,
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
@ -46,7 +46,7 @@ internal class DefaultTokensRepository(
storeAndPushTokens(userWalletId, response)
}
override suspend fun getSingleCurrencyWalletToken(userWalletId: UserWalletId): Token {
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
val userWallet = withContext(dispatchers.io) {
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
@ -56,11 +56,13 @@ internal class DefaultTokensRepository(
"Single currency wallet excepted, but multi currency wallet was found: $userWalletId"
}
return cardTokensFactory
.createPrimaryTokenForSingleCurrencyCard(userWallet.scanResponse)
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
}
override fun getMultiCurrencyWalletTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>> {
override fun getMultiCurrencyWalletCurrencies(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<CryptoCurrency>> {
return channelFlow {
val userWallet = withContext(dispatchers.io) {
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
@ -72,7 +74,7 @@ internal class DefaultTokensRepository(
}
launch(dispatchers.io) {
getMultiCurrencyWalletTokens(userWallet).collectLatest(::send)
getMultiCurrencyWalletCurrencies(userWallet).collectLatest(::send)
}
launch(dispatchers.io) {
@ -93,9 +95,9 @@ internal class DefaultTokensRepository(
.flowOn(dispatchers.io)
}
private fun getMultiCurrencyWalletTokens(userWallet: UserWallet): Flow<Set<Token>> {
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<Set<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseTokensFactory.createTokens(
responseCurrenciesFactory.createTokens(
response = storedTokens,
card = userWallet.scanResponse.card,
)
@ -131,7 +133,9 @@ internal class DefaultTokensRepository(
if (NOT_FOUND_HTTP_CODE in errorMessage) {
val response = userTokensStore.getSyncOrNull(userWallet.walletId)
?: userTokensResponseFactory.createUserTokensResponse(
tokens = cardTokensFactory.createDefaultTokensForMultiCurrencyCard(userWallet.scanResponse.card),
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(
userWallet.scanResponse.card,
),
isGroupedByNetwork = false,
isSortedByBalance = false,
)

View file

@ -1,9 +1,9 @@
package com.tangem.data.tokens.repository
import com.tangem.data.tokens.mock.MockNetworks
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -19,7 +19,7 @@ internal class MockNetworksRepository : NetworksRepository {
override fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
networks: Map<Network.ID, Set<CryptoCurrency.ID>>,
refresh: Boolean,
): Flow<Set<NetworkStatus>> {
return flowOf(

View file

@ -1,18 +1,18 @@
package com.tangem.data.tokens.repository
import com.tangem.data.tokens.mock.MockQuotes
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.QuotesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
internal class MockQuotesRepository : QuotesRepository {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>> {
override fun getQuotes(tokensIds: Set<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
return flowOf(
MockQuotes.quotes
.filter { it.tokenId in tokensIds }
.filter { it.currencyId in tokensIds }
.toSet(),
)
}

View file

@ -6,13 +6,13 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.tokens.model.Token as DomainToken
internal class CardTokensFactory(private val demoConfig: DemoConfig) {
internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createDefaultTokensForMultiCurrencyCard(card: CardDTO): Set<DomainToken> {
fun createDefaultCoinsForMultiCurrencyCard(card: CardDTO): Set<CryptoCurrency.Coin> {
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
@ -26,7 +26,7 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
return blockchains.mapNotNull { createCoin(it, card) }.toSet()
}
fun createPrimaryTokenForSingleCurrencyCard(scanResponse: ScanResponse): DomainToken {
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
val card = scanResponse.card
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
@ -41,13 +41,13 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
return primaryToken ?: coin
}
private fun createToken(sdkToken: SdkToken, blockchain: Blockchain, card: CardDTO): DomainToken? {
private fun createToken(sdkToken: SdkToken, blockchain: Blockchain, card: CardDTO): CryptoCurrency.Token? {
if (blockchain != Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return DomainToken(
return CryptoCurrency.Token(
id = getTokenId(blockchain, sdkToken),
networkId = getNetworkId(blockchain),
name = sdkToken.name,
@ -60,21 +60,19 @@ internal class CardTokensFactory(private val demoConfig: DemoConfig) {
)
}
private fun createCoin(blockchain: Blockchain, card: CardDTO): DomainToken? {
private fun createCoin(blockchain: Blockchain, card: CardDTO): CryptoCurrency.Coin? {
if (blockchain != Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return DomainToken(
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = blockchain.fullName,
symbol = blockchain.currency,
iconUrl = getCoinIconUrl(blockchain),
decimals = blockchain.decimals(),
isCustom = false,
contractAddress = null,
derivationPath = getDerivationPath(blockchain, card),
)
}

View file

@ -0,0 +1,77 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> {
return response.tokens.mapNotNull { createToken(it, card) }.toSet()
}
private fun createToken(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")
return null
}
if (demoConfig.isDemoCardId(card.cardId)) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
val sdkToken = createSdkToken(responseToken)
return if (sdkToken == null) {
createCoin(blockchain, responseToken)
} else {
createToken(blockchain, sdkToken, responseToken.derivationPath)
}
}
private fun createSdkToken(token: UserTokensResponse.Token): SdkToken? {
return token.contractAddress?.let { contractAddress ->
SdkToken(
name = token.name,
symbol = token.symbol,
contractAddress = contractAddress,
decimals = token.decimals,
id = token.id,
)
}
}
private fun createCoin(blockchain: Blockchain, responseToken: UserTokensResponse.Token): CryptoCurrency.Coin {
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = responseToken.name,
symbol = responseToken.symbol,
decimals = responseToken.decimals,
derivationPath = responseToken.derivationPath,
iconUrl = getCoinIconUrl(blockchain),
)
}
private fun createToken(blockchain: Blockchain, sdkToken: Token, derivationPath: String?): CryptoCurrency.Token {
val id = getTokenId(blockchain, sdkToken)
return CryptoCurrency.Token(
id = id,
networkId = getNetworkId(blockchain),
name = sdkToken.name,
symbol = sdkToken.symbol,
decimals = sdkToken.decimals,
derivationPath = derivationPath,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
contractAddress = sdkToken.contractAddress,
isCustom = isCustomToken(id),
)
}
}

View file

@ -1,60 +0,0 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.Token
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseTokensFactory(private val demoConfig: DemoConfig) {
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<Token> {
return response.tokens.mapNotNull { createToken(it, card) }.toSet()
}
private fun createToken(token: UserTokensResponse.Token, card: CardDTO): Token? {
var blockchain = Blockchain.fromNetworkId(token.networkId)
if (blockchain == null) {
Timber.e("Unable to find a blockchain with the network ID: ${token.networkId}")
return null
}
if (demoConfig.isDemoCardId(card.cardId)) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
val sdkToken = createSdkToken(token)
val (tokenId, iconUrl) = if (sdkToken == null) {
getCoinId(blockchain) to getCoinIconUrl(blockchain)
} else {
getTokenId(blockchain, sdkToken) to getTokenIconUrl(blockchain, sdkToken)
}
return Token(
id = tokenId,
networkId = getNetworkId(blockchain),
name = token.name,
symbol = token.symbol,
decimals = token.decimals,
iconUrl = iconUrl,
contractAddress = token.contractAddress,
derivationPath = token.derivationPath,
isCustom = isCustomToken(tokenId),
)
}
private fun createSdkToken(token: UserTokensResponse.Token): SdkToken? {
return token.contractAddress?.let { contractAddress ->
SdkToken(
name = token.name,
symbol = token.symbol,
contractAddress = contractAddress,
decimals = token.decimals,
id = token.id,
)
}
}
}

View file

@ -6,9 +6,9 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.tokens.model.Token as DomainToken
private const val DEFAULT_TOKENS_ICONS_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins"
private const val TOKEN_ICON_SIZE = "large"
@ -19,7 +19,7 @@ private const val TOKEN_ID_PREFIX = "token_"
private const val CUSTOM_TOKEN_ID_PREFIX = "custom_token_"
private const val TOKEN_ID_DELIMITER = '#'
internal fun isCustomToken(tokenId: DomainToken.ID): Boolean {
internal fun isCustomToken(tokenId: CryptoCurrency.ID): Boolean {
return tokenId.value.startsWith(CUSTOM_TOKEN_ID_PREFIX)
}
@ -41,17 +41,17 @@ internal fun getNetworkId(blockchain: Blockchain): Network.ID {
return Network.ID(value)
}
internal fun getCoinId(blockchain: Blockchain): DomainToken.ID {
internal fun getCoinId(blockchain: Blockchain): CryptoCurrency.ID {
return getTokenOrCoinId(blockchain, token = null)
}
internal fun getTokenId(blockchain: Blockchain, token: SdkToken): DomainToken.ID {
internal fun getTokenId(blockchain: Blockchain, token: SdkToken): CryptoCurrency.ID {
return getTokenOrCoinId(blockchain, token)
}
internal fun getResponseTokenId(token: DomainToken): String? {
return token.id.value.substringAfter(TOKEN_ID_DELIMITER)
.takeUnless { token.isCustom }
internal fun getResponseTokenId(currency: CryptoCurrency): String? {
return currency.id.value.substringAfter(TOKEN_ID_DELIMITER)
.takeUnless { currency is CryptoCurrency.Token && currency.isCustom }
}
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
@ -74,7 +74,7 @@ internal fun getCoinIconUrl(blockchain: Blockchain): String? {
return coinId?.let(::getTokenIconUrlFromDefaultHost)
}
private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): DomainToken.ID {
private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): CryptoCurrency.ID {
val sdkTokenId = token?.id
val (prefix, suffix) = when {
token == null -> COIN_ID_PREFIX to blockchain.toCoinId()
@ -89,7 +89,7 @@ private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): DomainTo
append(suffix.lowercase())
}
return DomainToken.ID(value)
return CryptoCurrency.ID(value)
}
private fun getTokenIconUrlFromDefaultHost(tokenId: String): String {

View file

@ -2,17 +2,17 @@ package com.tangem.data.tokens.utils
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.model.CryptoCurrency
internal class UserTokensResponseFactory {
fun createUserTokensResponse(
tokens: Set<Token>,
currencies: Set<CryptoCurrency>,
isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean,
): UserTokensResponse {
return UserTokensResponse(
tokens = tokens.map(::createResponseToken),
tokens = currencies.map(::createResponseToken),
group = if (isGroupedByNetwork) {
UserTokensResponse.GroupType.NETWORK
} else {
@ -26,17 +26,17 @@ internal class UserTokensResponseFactory {
)
}
private fun createResponseToken(domainToken: Token): UserTokensResponse.Token {
val blockchain = getBlockchain(domainToken.networkId)
private fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token {
val blockchain = getBlockchain(currency.networkId)
return UserTokensResponse.Token(
id = getResponseTokenId(domainToken),
id = getResponseTokenId(currency),
networkId = blockchain.toNetworkId(),
derivationPath = domainToken.derivationPath,
name = domainToken.name,
symbol = domainToken.symbol,
decimals = domainToken.decimals,
contractAddress = domainToken.contractAddress,
derivationPath = currency.derivationPath,
name = currency.name,
symbol = currency.symbol,
decimals = currency.decimals,
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
)
}
}