diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt new file mode 100644 index 0000000000..1165379d12 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/local/LocalTokenModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt new file mode 100644 index 0000000000..c04690b7ce --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensResponseStore.kt @@ -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( + key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt new file mode 100644 index 0000000000..df802c591c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensResponseStore.kt @@ -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? +} \ No newline at end of file diff --git a/data/common/build.gradle.kts b/data/common/build.gradle.kts index 96dc4861ca..05ffe54f76 100644 --- a/data/common/build.gradle.kts +++ b/data/common/build.gradle.kts @@ -14,9 +14,11 @@ dependencies { implementation(projects.core.datasource) /* Domain */ - implementation(projects.domain.models) + implementation(projects.domain.demo) implementation(projects.domain.legacy) + implementation(projects.domain.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) /* Libs - SDK */ implementation(tangemDeps.blockchain) @@ -28,8 +30,16 @@ dependencies { kapt(deps.hilt.kapt) /* Libs - Other */ - implementation(deps.kotlin.coroutines) - implementation(deps.jodatime) - implementation(deps.timber) + implementation(deps.androidx.datastore) 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) } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt new file mode 100644 index 0000000000..fca4ffba1a --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/CardCryptoCurrencyFactory.kt @@ -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 + + /** + * Create default coins for multi currency card + * + * @param scanResponse scan response + */ + fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List + + /** + * 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 +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt new file mode 100644 index 0000000000..c4da63c87c --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt @@ -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 { + 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 { + 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 { + return with(getSingleWalletCurrencies(scanResponse)) { + listOfNotNull(coin, primaryToken) + } + } + + private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List { + 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?) +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt new file mode 100644 index 0000000000..c9f546e6dd --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt new file mode 100644 index 0000000000..e6b868dc39 --- /dev/null +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt @@ -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() + + 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() + + 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() + + 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() + + 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)) + } + } + } +} \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 84780326f4..f5755416bd 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -6,12 +6,12 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toNetworkId 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.getBlockchain import com.tangem.data.common.utils.retryOnError import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher 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.tangemTech.TangemTechApi 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.supportedTokens 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.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.repository.ManageTokensRepository @@ -48,12 +47,12 @@ internal class DefaultManageTokensRepository( private val appPreferencesStore: AppPreferencesStore, private val testnetTokensStorage: TestnetTokensStorage, private val excludedBlockchains: ExcludedBlockchains, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, ) : ManageTokensRepository { private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains) // region getTokenListBatchFlow override fun getTokenListBatchFlow( @@ -77,7 +76,7 @@ internal class DefaultManageTokensRepository( prefetchDistance = batchSize, batchSize = batchSize, 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) { fetchTestnetCurrencies(userWallet, request) @@ -190,17 +189,11 @@ internal class DefaultManageTokensRepository( private fun createDefaultUserTokensResponse(userWallet: UserWallet) = userTokensResponseFactory.createUserTokensResponse( - currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), + currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), isGroupedByNetwork = 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 { return userWallet?.scanResponse?.let { it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains) @@ -261,7 +254,7 @@ internal class DefaultManageTokensRepository( userWalletId: UserWalletId, sourceNetwork: SourceNetwork, ): CurrencyUnsupportedState? { - val userWallet = getUserWallet(userWalletId = userWalletId) + val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val blockchain = getBlockchain(sourceNetwork.id) return when (sourceNetwork) { is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain) @@ -274,7 +267,7 @@ internal class DefaultManageTokensRepository( rawNetworkId: String, isMainNetwork: Boolean, ): CurrencyUnsupportedState? { - val userWallet = getUserWallet(userWalletId = userWalletId) + val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId) ?: error("Can not create blockchain with given networkId -> $rawNetworkId") return if (isMainNetwork) { diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 3f64ae5970..73044971f5 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -1,6 +1,7 @@ package com.tangem.data.managetokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher @@ -32,15 +33,17 @@ internal object ManageTokensDataModule { testnetTokensStorage: TestnetTokensStorage, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): ManageTokensRepository { return DefaultManageTokensRepository( - tangemTechApi, - userWalletsStore, - manageTokensUpdateFetcher, - appPreferencesStore, - testnetTokensStorage, - excludedBlockchains, - dispatchers, + tangemTechApi = tangemTechApi, + userWalletsStore = userWalletsStore, + manageTokensUpdateFetcher = manageTokensUpdateFetcher, + appPreferencesStore = appPreferencesStore, + testnetTokensStorage = testnetTokensStorage, + excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + dispatchers = dispatchers, ) } diff --git a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt index 6f72de7745..fed32a810e 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcher.kt @@ -1,27 +1,14 @@ package com.tangem.data.networks.single import arrow.core.Either -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.networks.store.NetworksStatusesStoreV2 -import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory 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.demo.DemoConfig import com.tangem.domain.networks.single.SingleNetworkStatusFetcher 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.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber @@ -30,27 +17,20 @@ import javax.inject.Inject /** * Default implementation of [SingleNetworkStatusFetcher] * - * @param excludedBlockchains excluded blockchains - * @property walletManagersFacade wallet managers facade - * @property networksStatusesStore networks statuses store - * @property userWalletsStore user wallets store - * @property appPreferencesStore app preferences store - * @property dispatchers dispatchers + * @property walletManagersFacade wallet managers facade + * @property networksStatusesStore networks statuses store + * @property cardCryptoCurrencyFactory card crypto currency factory + * @property dispatchers dispatchers * [REDACTED_AUTHOR] */ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( - excludedBlockchains: ExcludedBlockchains, private val walletManagersFacade: WalletManagersFacade, private val networksStatusesStore: NetworksStatusesStoreV2, - private val userWalletsStore: UserWalletsStore, - private val appPreferencesStore: AppPreferencesStore, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, ) : SingleNetworkStatusFetcher { - private val demoConfig = DemoConfig() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) - private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val networkStatusFactory = NetworkStatusFactory() 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) } - val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) - val networkCurrencies = createCurrencies(userWallet = userWallet, network = params.network) + val networkCurrencies = cardCryptoCurrencyFactory.create( + userWalletId = params.userWalletId, + network = params.network, + ) val result = withContext(dispatchers.io) { walletManagersFacade.update( @@ -92,36 +74,4 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor( Timber.e("Failed to fetch network status for $params: $it") networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network) } - - private suspend fun createCurrencies(userWallet: UserWallet, network: Network): List { - 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 { - val response = appPreferencesStore.getObjectSyncOrNull( - 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, - ) - } } \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt index 7f2a925235..a0da0c0c96 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusFetcherTest.kt @@ -2,8 +2,8 @@ package com.tangem.data.networks.single import com.google.common.truth.Truth 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.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.walletmanager.WalletManagersFacade @@ -22,39 +22,35 @@ import org.junit.Test */ internal class DefaultSingleNetworkStatusFetcherTest { - private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true) - private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true) - private val userWalletsStore: UserWalletsStore = mockk(relaxed = true) + private val walletManagersFacade: WalletManagersFacade = mockk(relaxUnitFun = true) + private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true) + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk() private val fetcher = DefaultSingleNetworkStatusFetcher( - excludedBlockchains = mockk(relaxed = true), walletManagersFacade = walletManagersFacade, networksStatusesStore = networksStatusesStore, - userWalletsStore = userWalletsStore, - appPreferencesStore = mockk(relaxed = true), + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, dispatchers = TestingCoroutineDispatcherProvider(), ) @Test fun `fetch network status successfully`() = runTest { - val params = SingleNetworkStatusFetcher.Params( - userWalletId = userWalletId, - network = network, - applyRefresh = true, - ) + val params = createParams() + + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum) 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) coVerifyOrder { - networksStatusesStore.refresh(userWalletId = userWalletId, network = network) - userWalletsStore.getSyncStrict(key = userWalletId) - walletManagersFacade.update(userWalletId, network, emptySet()) + networksStatusesStore.refresh(params.userWalletId, params.network) + cardCryptoCurrencyFactory.create(params.userWalletId, params.network) + walletManagersFacade.update(params.userWalletId, params.network, emptySet()) networksStatusesStore.storeSuccess( - userWalletId = userWalletId, - value = NetworkStatus(network, NetworkStatus.MissedDerivation), + userWalletId = params.userWalletId, + value = NetworkStatus(params.network, NetworkStatus.MissedDerivation), ) } @@ -63,21 +59,17 @@ internal class DefaultSingleNetworkStatusFetcherTest { @Test fun `fetch network status failure`() = runTest { - val params = SingleNetworkStatusFetcher.Params( - userWalletId = userWalletId, - network = network, - applyRefresh = true, - ) + val params = createParams() val exception = IllegalStateException() - coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws exception val actual = fetcher(params) coVerifyOrder { - networksStatusesStore.refresh(userWalletId = userWalletId, network = network) - userWalletsStore.getSyncStrict(key = userWalletId) - networksStatusesStore.storeError(userWalletId = userWalletId, network = network) + networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network) + cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network) + networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network) } coVerify(inverse = true) { @@ -91,23 +83,21 @@ internal class DefaultSingleNetworkStatusFetcherTest { @Test fun `fetch network status if applyRefresh is false`() = runTest { - val params = SingleNetworkStatusFetcher.Params( - userWalletId = userWalletId, - network = network, - applyRefresh = false, - ) + val params = createParams(applyRefresh = false) + + coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum) 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) coVerifyOrder { - userWalletsStore.getSyncStrict(key = userWalletId) - walletManagersFacade.update(userWalletId, network, emptySet()) + cardCryptoCurrencyFactory.create(params.userWalletId, params.network) + walletManagersFacade.update(params.userWalletId, params.network, emptySet()) networksStatusesStore.storeSuccess( - userWalletId = userWalletId, - value = NetworkStatus(network, NetworkStatus.MissedDerivation), + userWalletId = params.userWalletId, + value = NetworkStatus(params.network, NetworkStatus.MissedDerivation), ) } @@ -118,8 +108,16 @@ internal class DefaultSingleNetworkStatusFetcherTest { 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 { - val userWalletId = UserWalletId("011") - val network = MockCryptoCurrencyFactory().ethereum.network + + val ethereum = MockCryptoCurrencyFactory().ethereum } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 1172126125..af3cf30f5f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.tokens.repository.* import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader @@ -33,6 +34,7 @@ internal object TokensDataModule { dispatchers: CoroutineDispatcherProvider, expressServiceLoader: ExpressServiceLoader, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): CurrenciesRepository { return DefaultCurrenciesRepository( tangemTechApi = tangemTechApi, @@ -43,6 +45,7 @@ internal object TokensDataModule { expressServiceLoader = expressServiceLoader, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, ) } @@ -74,6 +77,7 @@ internal object TokensDataModule { cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, + cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ): NetworksRepository { return DefaultNetworksRepository( networksStatusesStore = networksStatusesStore, @@ -83,6 +87,7 @@ internal object TokensDataModule { cacheRegistry = cacheRegistry, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 9454dd4a87..d68c468811 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.* import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.cache.CacheRegistry 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.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.ApiResponseError @@ -51,12 +50,12 @@ internal class DefaultCurrenciesRepository( private val expressServiceLoader: ExpressServiceLoader, private val dispatchers: CoroutineDispatcherProvider, private val excludedBlockchains: ExcludedBlockchains, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, ) : CurrenciesRepository { private val demoConfig = DemoConfig() private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) @@ -223,7 +222,7 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh) currency } @@ -237,8 +236,8 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currencies = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken( - userWallet.scanResponse, + val currencies = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, ) fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh) currencies @@ -253,7 +252,9 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false) - val currency = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, + ) .find { it.id == id } requireNotNull(currency) { "Unable to find currency with provided ID: $id" } fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency)) @@ -672,7 +673,7 @@ internal class DefaultCurrenciesRepository( private fun createDefaultUserTokensResponse(userWallet: UserWallet) = userTokensResponseFactory.createUserTokensResponse( - currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), + currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse), isGroupedByNetwork = false, isSortedByBalance = false, ) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index b85018c34b..80acea39bf 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -5,8 +5,8 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId 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.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.NetworkStatusFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse 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.userwallet.UserWalletsStore 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.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network @@ -38,12 +37,11 @@ internal class DefaultNetworksRepository( private val userWalletsStore: UserWalletsStore, private val appPreferencesStore: AppPreferencesStore, private val cacheRegistry: CacheRegistry, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, ) : NetworksRepository { - private val demoConfig = DemoConfig() - private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains) private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) private val networkStatusFactory = NetworkStatusFactory() @@ -225,10 +223,14 @@ internal class DefaultNetworksRepository( responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence() } else { if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { - cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse) + cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken( + scanResponse = userWallet.scanResponse, + ) .asSequence() } else { - val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) + val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard( + scanResponse = userWallet.scanResponse, + ) sequenceOf(currency) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt deleted file mode 100644 index 1e3ed23c01..0000000000 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCryptoCurrenciesFactory.kt +++ /dev/null @@ -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 { - 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 { - 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) - } -} \ No newline at end of file