Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-07 15:50:08 +04:00
parent 9cda183a5b
commit 3171058cb6
16 changed files with 796 additions and 222 deletions

View file

@ -0,0 +1,21 @@
package com.tangem.datasource.di.local
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.DefaultUserTokensResponseStore
import com.tangem.datasource.local.token.UserTokensResponseStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object LocalTokenModule {
@Provides
@Singleton
fun provideUserTokensResponseStore(appPreferencesStore: AppPreferencesStore): UserTokensResponseStore {
return DefaultUserTokensResponseStore(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.domain.wallets.models.UserWalletId
/**
* Default implementation of [UserTokensResponseStore]
*
* @property appPreferencesStore app preferences store
*
[REDACTED_AUTHOR]
*/
internal class DefaultUserTokensResponseStore(
private val appPreferencesStore: AppPreferencesStore,
) : UserTokensResponseStore {
override suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse? {
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.wallets.models.UserWalletId
/**
* Store of [UserTokensResponse]
*
[REDACTED_AUTHOR]
*/
interface UserTokensResponseStore {
/** Get [UserTokensResponse] synchronously by [userWalletId] or null */
suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse?
}

View file

@ -14,9 +14,11 @@ dependencies {
implementation(projects.core.datasource) implementation(projects.core.datasource)
/* Domain */ /* Domain */
implementation(projects.domain.models) implementation(projects.domain.demo)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.models)
implementation(projects.domain.tokens.models) implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
/* Libs - SDK */ /* Libs - SDK */
implementation(tangemDeps.blockchain) implementation(tangemDeps.blockchain)
@ -28,8 +30,16 @@ dependencies {
kapt(deps.hilt.kapt) kapt(deps.hilt.kapt)
/* Libs - Other */ /* Libs - Other */
implementation(deps.kotlin.coroutines) implementation(deps.androidx.datastore)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.arrow.core) implementation(deps.arrow.core)
implementation(deps.jodatime)
implementation(deps.kotlin.coroutines)
implementation(deps.timber)
/* Test */
testImplementation(projects.common.test)
testImplementation(deps.test.coroutine)
testImplementation(deps.test.junit)
testImplementation(deps.test.mockk)
testImplementation(deps.test.truth)
} }

View file

@ -0,0 +1,46 @@
package com.tangem.data.common.currency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Factory for creating list of [CryptoCurrency] for selected card
*
[REDACTED_AUTHOR]
*/
interface CardCryptoCurrencyFactory {
/**
* Universal method for creating list of [CryptoCurrency] in [network] for any card
*
* @param userWalletId user wallet id that determines type of card
* @param network network
*/
@Throws
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
/**
* Create default coins for multi currency card
*
* @param scanResponse scan response
*/
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin>
/**
* Create primary currency for single currency card
*
* @param scanResponse scan response
*/
@Throws
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency
/**
* Create currencies for single currency card with token (like, NODL)
*
* @param scanResponse scan response
*/
@Throws
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency>
}

View file

@ -0,0 +1,128 @@
package com.tangem.data.common.currency
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
/**
* Default implementation of factory for creating list of [CryptoCurrency] for selected card
*
* @property demoConfig demo config
* @property excludedBlockchains excluded blockchains
* @property userWalletsStore user wallets store
* @property userTokensResponseStore user tokens response store
*/
internal class DefaultCardCryptoCurrencyFactory(
private val demoConfig: DemoConfig,
private val excludedBlockchains: ExcludedBlockchains,
private val userWalletsStore: UserWalletsStore,
private val userTokensResponseStore: UserTokensResponseStore,
) : CardCryptoCurrencyFactory {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
override suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
// multi-currency wallet
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
// check if the blockchain of single-currency wallet is the same as network
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
if (cardBlockchain != blockchain) return emptyList()
// single-currency wallet with token (NODL)
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
}
// single-currency wallet
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
}
override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
val card = scanResponse.card
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
}
return blockchains.mapNotNull {
cryptoCurrencyFactory.createCoin(
blockchain = it,
extraDerivationPath = null,
scanResponse = scanResponse,
)
}
}
override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
return with(getSingleWalletCurrencies(scanResponse)) {
primaryToken ?: coin
}
}
override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
return with(getSingleWalletCurrencies(scanResponse)) {
listOfNotNull(coin, primaryToken)
}
}
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
?: return emptyList()
val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
return responseCurrenciesFactory.createCurrencies(
tokens = response.tokens.filter {
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
},
scanResponse = userWallet.scanResponse,
)
}
private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies {
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = cryptoCurrencyFactory.createCoin(
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
val primaryToken = resolver.getPrimaryToken()?.let { token ->
cryptoCurrencyFactory.createToken(
sdkToken = token,
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
}
return SingleWalletCurrencies(coin = coin, primaryToken = primaryToken)
}
private data class SingleWalletCurrencies(val coin: CryptoCurrency, val primaryToken: CryptoCurrency?)
}

View file

@ -0,0 +1,33 @@
package com.tangem.data.common.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.demo.DemoConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object DataCommonModule {
@Provides
@Singleton
fun provideCardCryptoCurrencyFactory(
excludedBlockchains: ExcludedBlockchains,
userWalletsStore: UserWalletsStore,
userTokensResponseStore: UserTokensResponseStore,
): CardCryptoCurrencyFactory {
return DefaultCardCryptoCurrencyFactory(
demoConfig = DemoConfig(),
excludedBlockchains = excludedBlockchains,
userWalletsStore = userWalletsStore,
userTokensResponseStore = userTokensResponseStore,
)
}
}

View file

@ -0,0 +1,430 @@
package com.tangem.data.common.currency
import android.net.Uri
import com.google.common.truth.Truth
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.card.WalletData
import com.tangem.common.test.domain.card.MockScanResponseFactory
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.configs.GenericCardConfig
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class DefaultCardCryptoCurrencyFactoryTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val userTokensResponseStore: UserTokensResponseStore = mockk()
private val factory = DefaultCardCryptoCurrencyFactory(
demoConfig = DemoConfig(),
excludedBlockchains = ExcludedBlockchains(),
userWalletsStore = userWalletsStore,
userTokensResponseStore = userTokensResponseStore,
)
@Before
fun setup() {
mockkStatic(Uri::class)
every { Uri.parse(any()) } returns mockk()
}
@Test
fun `test create if userTokensResponse is not empty`() = runTest {
val multiWallet = createMultiWallet()
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
currencies = listOf(ethereum),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
}
val expected = listOf(ethereum)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if userTokensResponse is empty`() = runTest {
val multiWallet = createMultiWallet()
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
currencies = listOf(),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
}
val expected = emptyList<CryptoCurrency>()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if userTokensResponse is null`() = runTest {
val multiWallet = createMultiWallet()
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns null
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
}
val expected = emptyList<CryptoCurrency>()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if userTokensResponse does not contain currency of selected network`() = runTest {
val multiWallet = createMultiWallet()
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
currencies = listOf(bitcoin),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
}
val expected = emptyList<CryptoCurrency>()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if single wallet has another primary network`() = runTest {
val singleWallet = createSingleWallet()
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
val actual = factory.create(userWalletId = singleWallet.walletId, network = bitcoin.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
}
val expected = emptyList<CryptoCurrency>()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if card is single wallet`() = runTest {
val singleWallet = createSingleWallet()
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
coVerifyOrder {
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
}
val expected = listOf(ethereum)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test create if card is single wallet with token`() = runTest {
val singleWallet = createSingleWalletWithToken()
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
blockchain = Blockchain.Ethereum,
extraDerivationPath = null,
scanResponse = singleWallet.scanResponse,
)
val expected = listOf(ethereum, token)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createDefaultCoinsForMultiCurrencyCard if card is prod`() = runTest {
val multiWallet = createMultiWallet()
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
val expected = listOf(bitcoin, ethereum)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createDefaultCoinsForMultiCurrencyCard if card is test`() = runTest {
val multiWallet = createMultiWallet().let {
it.copy(
scanResponse = it.scanResponse.copy(
card = it.scanResponse.card.copy(cardId = "FF99", batchId = "99FF"),
),
)
}
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
val expected = listOf(
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet),
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createDefaultCoinsForMultiCurrencyCard if card is demo`() = runTest {
val multiWallet = createMultiWallet().let {
it.copy(
scanResponse = it.scanResponse.copy(
card = it.scanResponse.card.copy(cardId = "AC01000000041225"),
),
)
}
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
val expected = listOf(
bitcoin,
ethereum,
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin),
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createPrimaryCurrencyForSingleCurrencyCard if unable to create token`() = runTest {
val singleWallet = UserWallet(
name = "Note",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
isMultiCurrency = false,
scanResponse = MockScanResponseFactory.create(
cardConfig = GenericCardConfig(maxWalletCount = 2),
derivedKeys = emptyMap(),
),
hasBackupError = false,
)
val actual = runCatching {
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
}
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
Truth.assertThat(actual.isFailure).isTrue()
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
}
@Test
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is null`() = runTest {
val singleWallet = createSingleWallet()
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
val expected = ethereum
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is not null`() = runTest {
val singleWallet = createSingleWalletWithToken()
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
val expected = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
blockchain = Blockchain.Ethereum,
extraDerivationPath = null,
scanResponse = singleWallet.scanResponse,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createCurrenciesForSingleCurrencyCardWithToken if unable to create token`() = runTest {
val singleWalletWithToken = UserWallet(
name = "Note",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
isMultiCurrency = false,
scanResponse = MockScanResponseFactory.create(
cardConfig = GenericCardConfig(maxWalletCount = 2),
derivedKeys = emptyMap(),
),
hasBackupError = false,
)
val actual = runCatching {
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = singleWalletWithToken.scanResponse)
}
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
Truth.assertThat(actual.isFailure).isTrue()
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
}
@Test
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is null`() = runTest {
val singleWalletWithToken = createSingleWallet()
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = singleWalletWithToken.scanResponse,
)
val expected = listOf(ethereum)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is not null`() = runTest {
val singleWalletWithToken = createSingleWalletWithToken()
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = singleWalletWithToken.scanResponse,
)
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
sdkToken = singleWalletWithToken.scanResponse.cardTypesResolver.getPrimaryToken()!!,
blockchain = Blockchain.Ethereum,
extraDerivationPath = null,
scanResponse = singleWalletWithToken.scanResponse,
)
val expected = listOf(ethereum, token)
Truth.assertThat(actual).isEqualTo(expected)
}
private fun createMultiWallet(): UserWallet {
return UserWallet(
name = "Wallet 1",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
isMultiCurrency = true,
scanResponse = MockScanResponseFactory.create(
cardConfig = GenericCardConfig(maxWalletCount = 2),
derivedKeys = emptyMap(),
),
hasBackupError = false,
)
}
private fun createSingleWallet(): UserWallet {
return UserWallet(
name = "Note",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
isMultiCurrency = false,
scanResponse = MockScanResponseFactory.create(
cardConfig = GenericCardConfig(maxWalletCount = 2),
derivedKeys = emptyMap(),
).let {
it.copy(
card = it.card.copy(batchId = "AB10"),
productType = ProductType.Note,
)
},
hasBackupError = false,
)
}
private fun createSingleWalletWithToken(): UserWallet {
return UserWallet(
name = "NODL",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
isMultiCurrency = false,
scanResponse = MockScanResponseFactory.create(
cardConfig = GenericCardConfig(maxWalletCount = 2),
derivedKeys = emptyMap(),
).copy(
productType = ProductType.Note,
walletData = WalletData(
blockchain = "ETH",
token = WalletData.Token(
name = "Ethereum",
symbol = "ETH",
contractAddress = "0x",
decimals = 8,
),
),
),
hasBackupError = false,
)
}
private companion object {
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true)
val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin)
fun CryptoCurrency.setCanHandleTokens(value: Boolean): CryptoCurrency {
return when (this) {
is CryptoCurrency.Coin -> copy(network = network.copy(canHandleTokens = value))
is CryptoCurrency.Token -> copy(network = network.copy(canHandleTokens = value))
}
}
}
}

View file

@ -6,12 +6,12 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.currency.getBlockchain import com.tangem.data.common.currency.getBlockchain
import com.tangem.data.common.utils.retryOnError import com.tangem.data.common.utils.retryOnError
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
@ -26,7 +26,6 @@ import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.supportedTokens import com.tangem.domain.common.extensions.supportedTokens
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.managetokens.model.* import com.tangem.domain.managetokens.model.*
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository
@ -48,12 +47,12 @@ internal class DefaultManageTokensRepository(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val testnetTokensStorage: TestnetTokensStorage, private val testnetTokensStorage: TestnetTokensStorage,
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : ManageTokensRepository { ) : ManageTokensRepository {
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains) private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains)
private val userTokensResponseFactory = UserTokensResponseFactory() private val userTokensResponseFactory = UserTokensResponseFactory()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains)
// region getTokenListBatchFlow // region getTokenListBatchFlow
override fun getTokenListBatchFlow( override fun getTokenListBatchFlow(
@ -77,7 +76,7 @@ internal class DefaultManageTokensRepository(
prefetchDistance = batchSize, prefetchDistance = batchSize,
batchSize = batchSize, batchSize = batchSize,
subFetcher = { request, _, isFirstBatchFetching -> subFetcher = { request, _, isFirstBatchFetching ->
val userWallet = request.params.userWalletId?.let { getUserWallet(it) } val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict)
if (userWallet?.scanResponse?.card?.isTestCard == true) { if (userWallet?.scanResponse?.card?.isTestCard == true) {
fetchTestnetCurrencies(userWallet, request) fetchTestnetCurrencies(userWallet, request)
@ -190,17 +189,11 @@ internal class DefaultManageTokensRepository(
private fun createDefaultUserTokensResponse(userWallet: UserWallet) = private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createUserTokensResponse(
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
isGroupedByNetwork = false, isGroupedByNetwork = false,
isSortedByBalance = false, isSortedByBalance = false,
) )
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
}
}
private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> { private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> {
return userWallet?.scanResponse?.let { return userWallet?.scanResponse?.let {
it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains) it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains)
@ -261,7 +254,7 @@ internal class DefaultManageTokensRepository(
userWalletId: UserWalletId, userWalletId: UserWalletId,
sourceNetwork: SourceNetwork, sourceNetwork: SourceNetwork,
): CurrencyUnsupportedState? { ): CurrencyUnsupportedState? {
val userWallet = getUserWallet(userWalletId = userWalletId) val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = getBlockchain(sourceNetwork.id) val blockchain = getBlockchain(sourceNetwork.id)
return when (sourceNetwork) { return when (sourceNetwork) {
is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain) is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain)
@ -274,7 +267,7 @@ internal class DefaultManageTokensRepository(
rawNetworkId: String, rawNetworkId: String,
isMainNetwork: Boolean, isMainNetwork: Boolean,
): CurrencyUnsupportedState? { ): CurrencyUnsupportedState? {
val userWallet = getUserWallet(userWalletId = userWalletId) val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId) val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId)
?: error("Can not create blockchain with given networkId -> $rawNetworkId") ?: error("Can not create blockchain with given networkId -> $rawNetworkId")
return if (isMainNetwork) { return if (isMainNetwork) {

View file

@ -1,6 +1,7 @@
package com.tangem.data.managetokens.di package com.tangem.data.managetokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultCustomTokensRepository
import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
@ -32,15 +33,17 @@ internal object ManageTokensDataModule {
testnetTokensStorage: TestnetTokensStorage, testnetTokensStorage: TestnetTokensStorage,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
): ManageTokensRepository { ): ManageTokensRepository {
return DefaultManageTokensRepository( return DefaultManageTokensRepository(
tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore, userWalletsStore = userWalletsStore,
manageTokensUpdateFetcher, manageTokensUpdateFetcher = manageTokensUpdateFetcher,
appPreferencesStore, appPreferencesStore = appPreferencesStore,
testnetTokensStorage, testnetTokensStorage = testnetTokensStorage,
excludedBlockchains, excludedBlockchains = excludedBlockchains,
dispatchers, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
dispatchers = dispatchers,
) )
} }

View file

@ -1,27 +1,14 @@
package com.tangem.data.networks.single package com.tangem.data.networks.single
import arrow.core.Either import arrow.core.Either
import com.tangem.blockchain.common.Blockchain import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.networks.store.NetworksStatusesStoreV2 import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkStatusFactory import com.tangem.data.tokens.utils.NetworkStatusFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.utils.catchOn import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.tokens.model.CryptoCurrency 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.NetworkStatus
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -30,27 +17,20 @@ import javax.inject.Inject
/** /**
* Default implementation of [SingleNetworkStatusFetcher] * Default implementation of [SingleNetworkStatusFetcher]
* *
* @param excludedBlockchains excluded blockchains * @property walletManagersFacade wallet managers facade
* @property walletManagersFacade wallet managers facade * @property networksStatusesStore networks statuses store
* @property networksStatusesStore networks statuses store * @property cardCryptoCurrencyFactory card crypto currency factory
* @property userWalletsStore user wallets store * @property dispatchers dispatchers
* @property appPreferencesStore app preferences store
* @property dispatchers dispatchers
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
internal class DefaultSingleNetworkStatusFetcher @Inject constructor( internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
excludedBlockchains: ExcludedBlockchains,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val networksStatusesStore: NetworksStatusesStoreV2, private val networksStatusesStore: NetworksStatusesStoreV2,
private val userWalletsStore: UserWalletsStore, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : SingleNetworkStatusFetcher { ) : SingleNetworkStatusFetcher {
private val demoConfig = DemoConfig()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
private val networkStatusFactory = NetworkStatusFactory() private val networkStatusFactory = NetworkStatusFactory()
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) { override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) {
@ -58,8 +38,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
} }
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) val networkCurrencies = cardCryptoCurrencyFactory.create(
val networkCurrencies = createCurrencies(userWallet = userWallet, network = params.network) userWalletId = params.userWalletId,
network = params.network,
)
val result = withContext(dispatchers.io) { val result = withContext(dispatchers.io) {
walletManagersFacade.update( walletManagersFacade.update(
@ -92,36 +74,4 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
Timber.e("Failed to fetch network status for $params: $it") Timber.e("Failed to fetch network status for $params: $it")
networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network) networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
} }
private suspend fun createCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
// multi-currency wallet
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
// check if the blockchain of single-currency wallet is the same as network
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
if (cardBlockchain != blockchain) return emptyList()
// single-currency wallet with token (NODL)
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
return cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
}
// single-currency wallet
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
}
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
val response = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
) ?: return emptyList()
return responseCurrenciesFactory.createCurrencies(
tokens = response.tokens.filter {
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
},
scanResponse = userWallet.scanResponse,
)
}
} }

View file

@ -2,8 +2,8 @@ package com.tangem.data.networks.single
import com.google.common.truth.Truth import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.store.NetworksStatusesStoreV2 import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -22,39 +22,35 @@ import org.junit.Test
*/ */
internal class DefaultSingleNetworkStatusFetcherTest { internal class DefaultSingleNetworkStatusFetcherTest {
private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true) private val walletManagersFacade: WalletManagersFacade = mockk(relaxUnitFun = true)
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true) private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true)
private val userWalletsStore: UserWalletsStore = mockk(relaxed = true) private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val fetcher = DefaultSingleNetworkStatusFetcher( private val fetcher = DefaultSingleNetworkStatusFetcher(
excludedBlockchains = mockk(relaxed = true),
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
networksStatusesStore = networksStatusesStore, networksStatusesStore = networksStatusesStore,
userWalletsStore = userWalletsStore, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
appPreferencesStore = mockk(relaxed = true),
dispatchers = TestingCoroutineDispatcherProvider(), dispatchers = TestingCoroutineDispatcherProvider(),
) )
@Test @Test
fun `fetch network status successfully`() = runTest { fun `fetch network status successfully`() = runTest {
val params = SingleNetworkStatusFetcher.Params( val params = createParams()
userWalletId = userWalletId,
network = network, coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum)
applyRefresh = true,
)
val result = UpdateWalletManagerResult.MissedDerivation val result = UpdateWalletManagerResult.MissedDerivation
coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result coEvery { walletManagersFacade.update(params.userWalletId, params.network, emptySet()) } returns result
val actual = fetcher(params) val actual = fetcher(params)
coVerifyOrder { coVerifyOrder {
networksStatusesStore.refresh(userWalletId = userWalletId, network = network) networksStatusesStore.refresh(params.userWalletId, params.network)
userWalletsStore.getSyncStrict(key = userWalletId) cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
walletManagersFacade.update(userWalletId, network, emptySet()) walletManagersFacade.update(params.userWalletId, params.network, emptySet())
networksStatusesStore.storeSuccess( networksStatusesStore.storeSuccess(
userWalletId = userWalletId, userWalletId = params.userWalletId,
value = NetworkStatus(network, NetworkStatus.MissedDerivation), value = NetworkStatus(params.network, NetworkStatus.MissedDerivation),
) )
} }
@ -63,21 +59,17 @@ internal class DefaultSingleNetworkStatusFetcherTest {
@Test @Test
fun `fetch network status failure`() = runTest { fun `fetch network status failure`() = runTest {
val params = SingleNetworkStatusFetcher.Params( val params = createParams()
userWalletId = userWalletId,
network = network,
applyRefresh = true,
)
val exception = IllegalStateException() val exception = IllegalStateException()
coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws exception
val actual = fetcher(params) val actual = fetcher(params)
coVerifyOrder { coVerifyOrder {
networksStatusesStore.refresh(userWalletId = userWalletId, network = network) networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
userWalletsStore.getSyncStrict(key = userWalletId) cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
networksStatusesStore.storeError(userWalletId = userWalletId, network = network) networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -91,23 +83,21 @@ internal class DefaultSingleNetworkStatusFetcherTest {
@Test @Test
fun `fetch network status if applyRefresh is false`() = runTest { fun `fetch network status if applyRefresh is false`() = runTest {
val params = SingleNetworkStatusFetcher.Params( val params = createParams(applyRefresh = false)
userWalletId = userWalletId,
network = network, coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum)
applyRefresh = false,
)
val result = UpdateWalletManagerResult.MissedDerivation val result = UpdateWalletManagerResult.MissedDerivation
coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result coEvery { walletManagersFacade.update(params.userWalletId, params.network, emptySet()) } returns result
val actual = fetcher(params) val actual = fetcher(params)
coVerifyOrder { coVerifyOrder {
userWalletsStore.getSyncStrict(key = userWalletId) cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
walletManagersFacade.update(userWalletId, network, emptySet()) walletManagersFacade.update(params.userWalletId, params.network, emptySet())
networksStatusesStore.storeSuccess( networksStatusesStore.storeSuccess(
userWalletId = userWalletId, userWalletId = params.userWalletId,
value = NetworkStatus(network, NetworkStatus.MissedDerivation), value = NetworkStatus(params.network, NetworkStatus.MissedDerivation),
) )
} }
@ -118,8 +108,16 @@ internal class DefaultSingleNetworkStatusFetcherTest {
Truth.assertThat(actual.isRight()).isTrue() Truth.assertThat(actual.isRight()).isTrue()
} }
private fun createParams(applyRefresh: Boolean = true): SingleNetworkStatusFetcher.Params {
return SingleNetworkStatusFetcher.Params(
userWalletId = UserWalletId("011"),
network = ethereum.network,
applyRefresh = applyRefresh,
)
}
private companion object { private companion object {
val userWalletId = UserWalletId("011")
val network = MockCryptoCurrencyFactory().ethereum.network val ethereum = MockCryptoCurrencyFactory().ethereum
} }
} }

View file

@ -2,6 +2,7 @@ package com.tangem.data.tokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.tokens.repository.* import com.tangem.data.tokens.repository.*
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
@ -33,6 +34,7 @@ internal object TokensDataModule {
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
expressServiceLoader: ExpressServiceLoader, expressServiceLoader: ExpressServiceLoader,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
): CurrenciesRepository { ): CurrenciesRepository {
return DefaultCurrenciesRepository( return DefaultCurrenciesRepository(
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
@ -43,6 +45,7 @@ internal object TokensDataModule {
expressServiceLoader = expressServiceLoader, expressServiceLoader = expressServiceLoader,
dispatchers = dispatchers, dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
) )
} }
@ -74,6 +77,7 @@ internal object TokensDataModule {
cacheRegistry: CacheRegistry, cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
): NetworksRepository { ): NetworksRepository {
return DefaultNetworksRepository( return DefaultNetworksRepository(
networksStatusesStore = networksStatusesStore, networksStatusesStore = networksStatusesStore,
@ -83,6 +87,7 @@ internal object TokensDataModule {
cacheRegistry = cacheRegistry, cacheRegistry = cacheRegistry,
dispatchers = dispatchers, dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
) )
} }

View file

@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.*
import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.* import com.tangem.data.common.currency.*
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
@ -51,12 +50,12 @@ internal class DefaultCurrenciesRepository(
private val expressServiceLoader: ExpressServiceLoader, private val expressServiceLoader: ExpressServiceLoader,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
) : CurrenciesRepository { ) : CurrenciesRepository {
private val demoConfig = DemoConfig() private val demoConfig = DemoConfig()
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
private val userTokensResponseFactory = UserTokensResponseFactory() private val userTokensResponseFactory = UserTokensResponseFactory()
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers)
@ -223,7 +222,7 @@ internal class DefaultCurrenciesRepository(
val userWallet = getUserWallet(userWalletId) val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh) fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh)
currency currency
} }
@ -237,8 +236,8 @@ internal class DefaultCurrenciesRepository(
val userWallet = getUserWallet(userWalletId) val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
val currencies = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken( val currencies = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
userWallet.scanResponse, scanResponse = userWallet.scanResponse,
) )
fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh) fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh)
currencies currencies
@ -253,7 +252,9 @@ internal class DefaultCurrenciesRepository(
val userWallet = getUserWallet(userWalletId) val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
val currency = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = userWallet.scanResponse,
)
.find { it.id == id } .find { it.id == id }
requireNotNull(currency) { "Unable to find currency with provided ID: $id" } requireNotNull(currency) { "Unable to find currency with provided ID: $id" }
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency)) fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency))
@ -672,7 +673,7 @@ internal class DefaultCurrenciesRepository(
private fun createDefaultUserTokensResponse(userWallet: UserWallet) = private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createUserTokensResponse(
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
isGroupedByNetwork = false, isGroupedByNetwork = false,
isSortedByBalance = false, isSortedByBalance = false,
) )

View file

@ -5,8 +5,8 @@ import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkStatusFactory import com.tangem.data.tokens.utils.NetworkStatusFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.network.NetworksStatusesStore import com.tangem.datasource.local.network.NetworksStatusesStore
@ -15,7 +15,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
@ -38,12 +37,11 @@ internal class DefaultNetworksRepository(
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
) : NetworksRepository { ) : NetworksRepository {
private val demoConfig = DemoConfig()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
private val networkStatusFactory = NetworkStatusFactory() private val networkStatusFactory = NetworkStatusFactory()
@ -225,10 +223,14 @@ internal class DefaultNetworksRepository(
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence() responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
} else { } else {
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = userWallet.scanResponse,
)
.asSequence() .asSequence()
} else { } else {
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
scanResponse = userWallet.scanResponse,
)
sequenceOf(currency) sequenceOf(currency)
} }

View file

@ -1,86 +0,0 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
class CardCryptoCurrenciesFactory(
private val demoConfig: DemoConfig,
excludedBlockchains: ExcludedBlockchains,
) {
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
val card = scanResponse.card
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
}
return blockchains.mapNotNull {
cryptoCurrencyFactory.createCoin(
blockchain = it,
extraDerivationPath = null,
scanResponse = scanResponse,
)
}
}
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = cryptoCurrencyFactory.createCoin(
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
val primaryToken = resolver.getPrimaryToken()?.let { token ->
cryptoCurrencyFactory.createToken(
sdkToken = token,
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
}
return primaryToken ?: coin
}
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = cryptoCurrencyFactory.createCoin(
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
val primaryToken = resolver.getPrimaryToken()?.let { token ->
cryptoCurrencyFactory.createToken(
sdkToken = token,
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,
)
}
return listOfNotNull(coin, primaryToken)
}
}