diff --git a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt index 34411415bf..788367340b 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.di.data -import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.sdk.api.TangemSdkManager @@ -21,9 +21,14 @@ internal object CardDataModule { fun providesDerivationsRepository( tangemSdkManager: TangemSdkManager, userWalletsStore: UserWalletsStore, - excludedBlockchains: ExcludedBlockchains, + networkFactory: NetworkFactory, dispatchers: CoroutineDispatcherProvider, ): DerivationsRepository { - return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, excludedBlockchains, dispatchers) + return DefaultDerivationsRepository( + tangemSdkManager = tangemSdkManager, + userWalletsStore = userWalletsStore, + networkFactory = networkFactory, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index c41b7b37f0..e888c4599d 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.card import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve @@ -11,7 +10,7 @@ import com.tangem.common.doOnSuccess import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.BackendId import com.tangem.domain.card.repository.DerivationsRepository @@ -33,7 +32,7 @@ private typealias DerivedKeys = Map internal class DefaultDerivationsRepository( private val tangemSdkManager: TangemSdkManager, private val userWalletsStore: UserWalletsStore, - private val excludedBlockchains: ExcludedBlockchains, + private val networkFactory: NetworkFactory, private val dispatchers: CoroutineDispatcherProvider, ) : DerivationsRepository { @@ -47,11 +46,10 @@ internal class DefaultDerivationsRepository( derivePublicKeysByNetworks( userWalletId = userWalletId, networks = networkIds.mapNotNull { - getNetwork( + networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) }, ) @@ -86,11 +84,10 @@ internal class DefaultDerivationsRepository( val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse) .findByNetworks( networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> - getNetwork( + networkFactory.create( blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, extraDerivationPath = extraDerivationPath, scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) }, ) diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index 7b9bd90813..c54a0f2e2f 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.common.CompletionResult import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.ScanCardException import com.tangem.domain.common.configs.GenericCardConfig @@ -32,7 +33,7 @@ internal class DefaultDerivationsRepositoryTest { tangemSdkManager = tangemSdkManager, userWalletsStore = userWalletsStore, dispatchers = TestingCoroutineDispatcherProvider(), - excludedBlockchains = ExcludedBlockchains(), + networkFactory = NetworkFactory(excludedBlockchains = ExcludedBlockchains()), ) private val defaultUserWalletId = UserWalletId("011") diff --git a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt index 753dd385bc..48fcf815ed 100644 --- a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt @@ -7,8 +7,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.currency.getNetworkDerivationPath -import com.tangem.data.common.currency.getNetworkStandardType +import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.configs.GenericCardConfig import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.currency.CryptoCurrency @@ -47,7 +46,7 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default } fun createCoin(blockchain: Blockchain): CryptoCurrency { - val derivationPath = getNetworkDerivationPath( + val derivationPath = createDerivationPath( blockchain = blockchain, extraDerivationPath = null, cardDerivationStyleProvider = scanResponse.derivationStyleProvider, @@ -119,6 +118,47 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default )!! } + private fun createDerivationPath( + blockchain: Blockchain, + extraDerivationPath: String?, + cardDerivationStyleProvider: DerivationStyleProvider?, + ): Network.DerivationPath { + if (cardDerivationStyleProvider == null) return Network.DerivationPath.None + + val defaultDerivationPath = getDefaultDerivationPath(blockchain, cardDerivationStyleProvider) + + return if (extraDerivationPath.isNullOrBlank()) { + if (defaultDerivationPath.isNullOrBlank()) { + Network.DerivationPath.None + } else { + Network.DerivationPath.Card(defaultDerivationPath) + } + } else { + if (extraDerivationPath == defaultDerivationPath) { + Network.DerivationPath.Card(defaultDerivationPath) + } else { + Network.DerivationPath.Custom(extraDerivationPath) + } + } + } + + private fun getDefaultDerivationPath( + blockchain: Blockchain, + derivationStyleProvider: DerivationStyleProvider, + ): String? { + return blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath + } + + private fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType { + return when (blockchain) { + Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.StandardType.ERC20 + Blockchain.BSC, Blockchain.BSCTestnet -> Network.StandardType.BEP20 + Blockchain.Binance, Blockchain.BinanceTestnet -> Network.StandardType.BEP2 + Blockchain.Tron, Blockchain.TronTestnet -> Network.StandardType.TRC20 + else -> Network.StandardType.Unspecified(blockchain.name) + } + } + private companion object { val defaultScanResponse = MockScanResponseFactory.create( diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt index 5d0757d5e8..a35bb78aed 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.data.common.network.NetworkFactory import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.scan.ScanResponse @@ -15,6 +16,8 @@ class CryptoCurrencyFactory( private val excludedBlockchains: ExcludedBlockchains, ) { + private val networkFactory by lazy(LazyThreadSafetyMode.NONE) { NetworkFactory(excludedBlockchains) } + @Suppress("LongParameterList") // Yep, it's long fun createToken( network: Network, @@ -49,7 +52,12 @@ class CryptoCurrencyFactory( return null } - val network = getNetwork(blockchain, extraDerivationPath, scanResponse, excludedBlockchains) ?: return null + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = extraDerivationPath, + scanResponse = scanResponse, + ) ?: return null + val id = getTokenId(network, sdkToken) return CryptoCurrency.Token( @@ -73,7 +81,12 @@ class CryptoCurrencyFactory( Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain") return null } - val network = getNetwork(blockchain, extraDerivationPath, scanResponse, excludedBlockchains) ?: return null + + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = extraDerivationPath, + scanResponse = scanResponse, + ) ?: return null return createCoin(network) } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt index c4abe4cac3..7642844e58 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt @@ -1,310 +1,8 @@ package com.tangem.data.common.currency import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.FeePaidCurrency -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.blockchainsdk.utils.toNetworkId -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.canHandleToken -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.network.Network -import com.tangem.domain.models.scan.ScanResponse -import timber.log.Timber fun getBlockchain(networkId: Network.ID): Blockchain { return Blockchain.fromId(networkId.rawId.value) -} - -fun getNetwork( - blockchain: Blockchain, - extraDerivationPath: String?, - derivationStyleProvider: DerivationStyleProvider?, - excludedBlockchains: ExcludedBlockchains, - canHandleTokens: Boolean, -): Network? { - if (!isBlockchainSupported(blockchain, excludedBlockchains)) { - return null - } - - val derivationPath = getNetworkDerivationPath( - blockchain = blockchain, - extraDerivationPath = extraDerivationPath, - cardDerivationStyleProvider = derivationStyleProvider, - ) - - return Network( - id = Network.ID(value = blockchain.id, derivationPath = derivationPath), - backendId = blockchain.toNetworkId(), - name = blockchain.fullName, - isTestnet = blockchain.isTestnet(), - derivationPath = derivationPath, - currencySymbol = blockchain.currency, - standardType = getNetworkStandardType(blockchain), - hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource, - canHandleTokens = canHandleTokens, - transactionExtrasType = blockchain.getSupportedTransactionExtras(), - ) -} - -fun getNetwork( - networkId: Network.ID, - derivationPath: Network.DerivationPath, - scanResponse: ScanResponse, - excludedBlockchains: ExcludedBlockchains, -): Network? { - val blockchain = getBlockchain(networkId) - if (!isBlockchainSupported(blockchain, excludedBlockchains)) { - return null - } - - return Network( - id = networkId, - backendId = blockchain.toNetworkId(), - name = blockchain.fullName, - isTestnet = blockchain.isTestnet(), - derivationPath = derivationPath, - currencySymbol = blockchain.currency, - standardType = getNetworkStandardType(blockchain), - hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource, - canHandleTokens = scanResponse.card.canHandleToken( - blockchain, - scanResponse.cardTypesResolver, - excludedBlockchains, - ), - transactionExtrasType = blockchain.getSupportedTransactionExtras(), - ) -} - -private fun isBlockchainSupported(blockchain: Blockchain, excludedBlockchains: ExcludedBlockchains): Boolean { - if (blockchain == Blockchain.Unknown) { - Timber.w("Unable to convert Unknown blockchain to the domain network model") - return false - } - if (blockchain in excludedBlockchains) { - Timber.w("Unable to convert excluded blockchain to the domain network model") - return false - } - - return true -} - -fun getNetwork( - blockchain: Blockchain, - extraDerivationPath: String?, - scanResponse: ScanResponse, - excludedBlockchains: ExcludedBlockchains, -): Network? { - return getNetwork( - blockchain = blockchain, - extraDerivationPath = extraDerivationPath, - derivationStyleProvider = scanResponse.derivationStyleProvider, - excludedBlockchains = excludedBlockchains, - canHandleTokens = scanResponse.card.canHandleToken( - blockchain, - scanResponse.cardTypesResolver, - excludedBlockchains, - ), - ) -} - -fun getNetworkDerivationPath( - blockchain: Blockchain, - extraDerivationPath: String?, - cardDerivationStyleProvider: DerivationStyleProvider?, -): Network.DerivationPath { - if (cardDerivationStyleProvider == null) { - return Network.DerivationPath.None - } - - val defaultDerivationPath = getDefaultDerivationPath(blockchain, cardDerivationStyleProvider) - - return if (extraDerivationPath.isNullOrBlank()) { - if (defaultDerivationPath.isNullOrBlank()) { - Network.DerivationPath.None - } else { - Network.DerivationPath.Card(defaultDerivationPath) - } - } else { - if (extraDerivationPath == defaultDerivationPath) { - Network.DerivationPath.Card(defaultDerivationPath) - } else { - Network.DerivationPath.Custom(extraDerivationPath) - } - } -} - -fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType { - return when (blockchain) { - Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.StandardType.ERC20 - Blockchain.BSC, Blockchain.BSCTestnet -> Network.StandardType.BEP20 - Blockchain.Binance, Blockchain.BinanceTestnet -> Network.StandardType.BEP2 - Blockchain.Tron, Blockchain.TronTestnet -> Network.StandardType.TRC20 - else -> Network.StandardType.Unspecified(blockchain.name) - } -} - -private fun getDefaultDerivationPath( - blockchain: Blockchain, - derivationStyleProvider: DerivationStyleProvider, -): String? { - return blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath -} - -@Suppress("LongMethod") -private fun Blockchain.getSupportedTransactionExtras(): Network.TransactionExtrasType { - return when (this) { - Blockchain.XRP -> Network.TransactionExtrasType.DESTINATION_TAG - Blockchain.Binance, - Blockchain.TON, - Blockchain.Cosmos, - Blockchain.TerraV1, - Blockchain.TerraV2, - Blockchain.Stellar, - Blockchain.Hedera, - Blockchain.Algorand, - Blockchain.Sei, - Blockchain.InternetComputer, - Blockchain.Casper, - -> Network.TransactionExtrasType.MEMO - // region Other blockchains - Blockchain.Unknown, - Blockchain.Alephium, - Blockchain.AlephiumTestnet, - Blockchain.Arbitrum, - Blockchain.ArbitrumTestnet, - Blockchain.Avalanche, - Blockchain.AvalancheTestnet, - Blockchain.BinanceTestnet, - Blockchain.BSC, - Blockchain.BSCTestnet, - Blockchain.Bitcoin, - Blockchain.BitcoinTestnet, - Blockchain.BitcoinCash, - Blockchain.BitcoinCashTestnet, - Blockchain.Cardano, - Blockchain.CosmosTestnet, - Blockchain.Dogecoin, - Blockchain.Ducatus, - Blockchain.Ethereum, - Blockchain.EthereumTestnet, - Blockchain.EthereumClassic, - Blockchain.EthereumClassicTestnet, - Blockchain.Fantom, - Blockchain.FantomTestnet, - Blockchain.Litecoin, - Blockchain.Near, - Blockchain.NearTestnet, - Blockchain.Polkadot, - Blockchain.PolkadotTestnet, - Blockchain.Kava, - Blockchain.KavaTestnet, - Blockchain.Kusama, - Blockchain.Polygon, - Blockchain.PolygonTestnet, - Blockchain.RSK, - Blockchain.SeiTestnet, - Blockchain.StellarTestnet, - Blockchain.Solana, - Blockchain.SolanaTestnet, - Blockchain.Tezos, - Blockchain.Tron, - Blockchain.TronTestnet, - Blockchain.Gnosis, - Blockchain.Dash, - Blockchain.Optimism, - Blockchain.OptimismTestnet, - Blockchain.Dischain, - Blockchain.EthereumPow, - Blockchain.EthereumPowTestnet, - Blockchain.Kaspa, - Blockchain.KaspaTestnet, - Blockchain.Telos, - Blockchain.TelosTestnet, - Blockchain.TONTestnet, - Blockchain.Ravencoin, - Blockchain.Clore, - Blockchain.RavencoinTestnet, - Blockchain.Cronos, - Blockchain.AlephZero, - Blockchain.AlephZeroTestnet, - Blockchain.OctaSpace, - Blockchain.OctaSpaceTestnet, - Blockchain.Chia, - Blockchain.ChiaTestnet, - Blockchain.Decimal, - Blockchain.DecimalTestnet, - Blockchain.XDC, - Blockchain.XDCTestnet, - Blockchain.VeChain, - Blockchain.VeChainTestnet, - Blockchain.Aptos, - Blockchain.AptosTestnet, - Blockchain.Playa3ull, - Blockchain.Shibarium, - Blockchain.ShibariumTestnet, - Blockchain.AlgorandTestnet, - Blockchain.HederaTestnet, - Blockchain.Aurora, - Blockchain.AuroraTestnet, - Blockchain.Areon, - Blockchain.AreonTestnet, - Blockchain.PulseChain, - Blockchain.PulseChainTestnet, - Blockchain.ZkSyncEra, - Blockchain.ZkSyncEraTestnet, - Blockchain.Nexa, - Blockchain.NexaTestnet, - Blockchain.Moonbeam, - Blockchain.MoonbeamTestnet, - Blockchain.Manta, - Blockchain.MantaTestnet, - Blockchain.PolygonZkEVM, - Blockchain.PolygonZkEVMTestnet, - Blockchain.Radiant, - Blockchain.Fact0rn, - Blockchain.Base, - Blockchain.BaseTestnet, - Blockchain.Moonriver, - Blockchain.MoonriverTestnet, - Blockchain.Mantle, - Blockchain.MantleTestnet, - Blockchain.Flare, - Blockchain.FlareTestnet, - Blockchain.Taraxa, - Blockchain.TaraxaTestnet, - Blockchain.Koinos, - Blockchain.KoinosTestnet, - Blockchain.Joystream, - Blockchain.Bittensor, - Blockchain.Filecoin, - Blockchain.Blast, - Blockchain.BlastTestnet, - Blockchain.Cyber, - Blockchain.CyberTestnet, - Blockchain.Sui, - Blockchain.SuiTestnet, - Blockchain.EnergyWebChain, - Blockchain.EnergyWebChainTestnet, - Blockchain.EnergyWebX, - Blockchain.EnergyWebXTestnet, - Blockchain.CasperTestnet, - Blockchain.Core, - Blockchain.CoreTestnet, - Blockchain.Xodex, - Blockchain.Canxium, - Blockchain.Chiliz, - Blockchain.ChilizTestnet, - Blockchain.VanarChain, - Blockchain.VanarChainTestnet, - Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet, - Blockchain.Bitrock, Blockchain.BitrockTestnet, - Blockchain.Sonic, Blockchain.SonicTestnet, - Blockchain.ApeChain, Blockchain.ApeChainTestnet, - Blockchain.Scroll, Blockchain.ScrollTestnet, - Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet, - Blockchain.Pepecoin, Blockchain.PepecoinTestnet, - -> Network.TransactionExtrasType.NONE - // endregion - } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt index a1de67cf5c..1d9b8a5818 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.currency.CryptoCurrency @@ -12,9 +13,9 @@ import com.tangem.domain.models.scan.ScanResponse import timber.log.Timber import com.tangem.blockchain.common.Token as SdkToken -class ResponseCryptoCurrenciesFactory( - private val excludedBlockchains: ExcludedBlockchains, -) { +class ResponseCryptoCurrenciesFactory(excludedBlockchains: ExcludedBlockchains) { + + private val networkFactory by lazy(LazyThreadSafetyMode.NONE) { NetworkFactory(excludedBlockchains) } fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency { return response.tokens @@ -75,11 +76,10 @@ class ResponseCryptoCurrenciesFactory( responseToken: UserTokensResponse.Token, scanResponse: ScanResponse, ): CryptoCurrency.Coin? { - val network = getNetwork( - blockchain, - responseToken.derivationPath, - scanResponse, - excludedBlockchains, + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = responseToken.derivationPath, + scanResponse = scanResponse, ) ?: return null return CryptoCurrency.Coin( @@ -111,11 +111,10 @@ class ResponseCryptoCurrenciesFactory( responseDerivationPath: String?, scanResponse: ScanResponse, ): CryptoCurrency.Token? { - val network = getNetwork( - blockchain, - responseDerivationPath, - scanResponse, - excludedBlockchains, + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = responseDerivationPath, + scanResponse = scanResponse, ) ?: return null val id = getTokenId(network, sdkToken) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt index f71d95b479..33ce8e4ec8 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -7,7 +7,8 @@ import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensSaver -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.currency.getBlockchain +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.managetokens.utils.TokenAddressesConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -38,6 +39,7 @@ internal class DefaultCustomTokensRepository( private val excludedBlockchains: ExcludedBlockchains, private val dispatchers: CoroutineDispatcherProvider, private val userTokensSaver: UserTokensSaver, + private val networkFactory: NetworkFactory, ) : CustomTokensRepository { private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains) @@ -74,7 +76,7 @@ internal class DefaultCustomTokensRepository( } storedCurrencies.tokens.none { token -> - Blockchain.fromId(networkId.rawId.value).toNetworkId() == token.networkId && + getBlockchain(networkId).toNetworkId() == token.networkId && derivationPath.value == token.derivationPath && contractAddress.equals(token.contractAddress, ignoreCase = true) } @@ -91,7 +93,11 @@ internal class DefaultCustomTokensRepository( "User wallet [$userWalletId] not found while finding token" } val network = requireNotNull( - getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains), + networkFactory.create( + networkId = networkId, + derivationPath = derivationPath, + scanResponse = userWallet.scanResponse, + ), ) { "Network [$networkId] not found while finding token" } @@ -143,7 +149,11 @@ internal class DefaultCustomTokensRepository( "User wallet [$userWalletId] not found while creating coin" } val network = requireNotNull( - getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains), + networkFactory.create( + networkId = networkId, + derivationPath = derivationPath, + scanResponse = userWallet.scanResponse, + ), ) { "Network [$networkId] not found while creating coin" } @@ -176,7 +186,11 @@ internal class DefaultCustomTokensRepository( "User wallet [$userWalletId] not found while creating custom token" } val network = requireNotNull( - getNetwork(networkId, derivationPath, userWallet.scanResponse, excludedBlockchains), + networkFactory.create( + networkId = networkId, + derivationPath = derivationPath, + scanResponse = userWallet.scanResponse, + ), ) { "Network [$networkId] not found while creating custom token" } @@ -246,11 +260,10 @@ internal class DefaultCustomTokensRepository( ) if (canHandleBlockchain) { - getNetwork( + networkFactory.create( blockchain = blockchain, extraDerivationPath = null, scanResponse = scanResponse, - excludedBlockchains = excludedBlockchains, ) } else { null 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 8bf6b8c53e..ab85a04c36 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 @@ -9,6 +9,7 @@ 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.network.NetworkFactory import com.tangem.data.common.utils.retryOnError import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory @@ -49,9 +50,10 @@ internal class DefaultManageTokensRepository( private val excludedBlockchains: ExcludedBlockchains, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val dispatchers: CoroutineDispatcherProvider, + networkFactory: NetworkFactory, ) : ManageTokensRepository { - private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains) + private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(networkFactory, excludedBlockchains) private val userTokensResponseFactory = UserTokensResponseFactory() // region getTokenListBatchFlow 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 7f82b1942a..1e3cc8eafe 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 @@ -3,6 +3,7 @@ package com.tangem.data.managetokens.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.managetokens.DefaultCustomTokensRepository import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher @@ -35,6 +36,7 @@ internal object ManageTokensDataModule { dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, + networkFactory: NetworkFactory, ): ManageTokensRepository { return DefaultManageTokensRepository( tangemTechApi = tangemTechApi, @@ -44,6 +46,7 @@ internal object ManageTokensDataModule { testnetTokensStorage = testnetTokensStorage, excludedBlockchains = excludedBlockchains, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + networkFactory = networkFactory, dispatchers = dispatchers, ) } @@ -58,6 +61,7 @@ internal object ManageTokensDataModule { dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, userTokensSaver: UserTokensSaver, + networkFactory: NetworkFactory, ): CustomTokensRepository { return DefaultCustomTokensRepository( tangemTechApi = tangemTechApi, @@ -67,6 +71,7 @@ internal object ManageTokensDataModule { excludedBlockchains = excludedBlockchains, dispatchers = dispatchers, userTokensSaver = userTokensSaver, + networkFactory = networkFactory, ) } } \ No newline at end of file diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt index 5d771b9108..eb12b523a3 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt @@ -8,8 +8,8 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.data.common.currency.getCoinId -import com.tangem.data.common.currency.getNetwork import com.tangem.data.common.currency.getTokenId +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig @@ -25,6 +25,7 @@ import com.tangem.domain.models.scan.ScanResponse import timber.log.Timber internal class ManagedCryptoCurrencyFactory( + private val networkFactory: NetworkFactory, private val excludedBlockchains: ExcludedBlockchains, ) { @@ -98,11 +99,10 @@ internal class ManagedCryptoCurrencyFactory( return null } - val network = getNetwork( + val network = networkFactory.create( blockchain = blockchain, extraDerivationPath = token.derivationPath, scanResponse = scanResponse, - excludedBlockchains = excludedBlockchains, ) ?: return null val contractAddress = token.contractAddress @@ -172,11 +172,10 @@ internal class ManagedCryptoCurrencyFactory( ?.takeUnless { it in excludedBlockchains } ?: return null - val network = getNetwork( - blockchain, - extraDerivationPath, - scanResponse?.derivationStyleProvider, - excludedBlockchains, + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = extraDerivationPath, + derivationStyleProvider = scanResponse?.derivationStyleProvider, canHandleTokens = scanResponse?.let { it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains) } ?: true, @@ -217,11 +216,10 @@ internal class ManagedCryptoCurrencyFactory( val blockchain = Blockchain.fromNetworkId(token.networkId) if (blockchain != null && blockchain !in excludedBlockchains) { - getNetwork( - blockchain, - token.derivationPath, - scanResponse?.derivationStyleProvider, - excludedBlockchains, + networkFactory.create( + blockchain = blockchain, + extraDerivationPath = token.derivationPath, + derivationStyleProvider = scanResponse?.derivationStyleProvider, canHandleTokens = scanResponse?.let { it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains) } ?: true, diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 23806afa4e..01cf2d40ff 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -8,7 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.utils.retryOnError import com.tangem.data.markets.analytics.MarketsDataAnalyticsEvent import com.tangem.data.markets.converters.* @@ -40,10 +40,11 @@ internal class DefaultMarketsTokenRepository( private val userWalletsStore: UserWalletsStore, private val dispatcherProvider: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, - private val excludedBlockchains: ExcludedBlockchains, private val cacheRegistry: CacheRegistry, private val tokenExchangesStore: RuntimeStateStore>, private val maxApyStore: RuntimeStateStore, + private val networkFactory: NetworkFactory, + excludedBlockchains: ExcludedBlockchains, ) : MarketsTokenRepository { private val tokenMarketInfoConverter: TokenMarketInfoConverter = TokenMarketInfoConverter(excludedBlockchains) @@ -252,11 +253,10 @@ internal class DefaultMarketsTokenRepository( scanResponse = userWallet.scanResponse, ) } else { - val currencyNetwork = getNetwork( + val currencyNetwork = networkFactory.create( blockchain = blockchain, extraDerivationPath = null, scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) ?: return null cryptoCurrencyFactory.createToken( diff --git a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt index 55879a5abb..1c1830b9cc 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt @@ -3,6 +3,7 @@ package com.tangem.data.markets.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.markets.DefaultMarketsTokenRepository import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -30,6 +31,7 @@ internal object MarketsDataModule { analyticsEventHandler: AnalyticsEventHandler, cacheRegistry: CacheRegistry, excludedBlockchains: ExcludedBlockchains, + networkFactory: NetworkFactory, ): MarketsTokenRepository { return DefaultMarketsTokenRepository( marketsApi = marketsApi, @@ -41,6 +43,7 @@ internal object MarketsDataModule { tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()), excludedBlockchains = excludedBlockchains, maxApyStore = RuntimeStateStore(defaultValue = null), + networkFactory = networkFactory, ) } } \ No newline at end of file diff --git a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt index f9c42ee0bc..0bef8fea06 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducer.kt @@ -1,7 +1,6 @@ package com.tangem.data.networks.multi -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.networks.store.NetworksStatusesStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.models.network.NetworkStatus @@ -19,14 +18,14 @@ import timber.log.Timber * @property params params * @property networksStatusesStore networks statuses store * @property userWalletsStore user wallets store - * @property excludedBlockchains excluded blockchains + * @property networkFactory network factory * @property dispatchers dispatchers */ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor( @Assisted val params: MultiNetworkStatusProducer.Params, private val networksStatusesStore: NetworksStatusesStore, private val userWalletsStore: UserWalletsStore, - private val excludedBlockchains: ExcludedBlockchains, + private val networkFactory: NetworkFactory, private val dispatchers: CoroutineDispatcherProvider, ) : MultiNetworkStatusProducer { @@ -34,8 +33,8 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor( get() = setOf() override fun produce(): Flow> { - return networksStatusesStore - .get(userWalletId = params.userWalletId) + return networksStatusesStore.get(userWalletId = params.userWalletId) + .distinctUntilChanged() .mapNotNull { statuses -> val userWallet = userWalletsStore.getSyncOrNull(params.userWalletId) @@ -45,11 +44,10 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor( } statuses.mapNotNullTo(hashSetOf()) { status -> - val network = getNetwork( + val network = networkFactory.create( networkId = status.id, derivationPath = status.id.derivationPath, scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) ?: return@mapNotNullTo null NetworkStatus(network = network, value = status.value) diff --git a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt index 56194275ce..feca7854ed 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/multi/DefaultMultiNetworkStatusProducerTest.kt @@ -1,12 +1,12 @@ package com.tangem.data.networks.multi import com.google.common.truth.Truth -import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.network.MockNetworkStatusFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.common.test.utils.getEmittedValues +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.networks.models.SimpleNetworkStatus import com.tangem.data.networks.store.NetworksStatusesStore import com.tangem.data.networks.toSimple @@ -15,247 +15,409 @@ import com.tangem.domain.common.configs.GenericCardConfig import com.tangem.domain.models.network.NetworkStatus import com.tangem.domain.networks.multi.MultiNetworkStatusProducer import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import io.mockk.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance /** [REDACTED_AUTHOR] */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class DefaultMultiNetworkStatusProducerTest { private val params = MultiNetworkStatusProducer.Params(userWalletId = userWallet.walletId) private val networksStatusesStore = mockk() private val userWalletsStore = mockk() - private val excludedBlockchains = mockk() + private val networkFactory = mockk() private val dispatchers = TestingCoroutineDispatcherProvider() private val producer = DefaultMultiNetworkStatusProducer( params = params, networksStatusesStore = networksStatusesStore, userWalletsStore = userWalletsStore, - excludedBlockchains = excludedBlockchains, + networkFactory = networkFactory, dispatchers = dispatchers, ) - @Before - fun setup() { - every { excludedBlockchains.contains(any()) } returns false + @BeforeEach + fun resetMocks() { + clearMocks(networksStatusesStore, userWalletsStore, networkFactory) } @Test - fun `test that flow is mapped for user wallet id from params`() = runTest { + fun `flow is mapped for user wallet id from params`() = runTest { + // Assert val statuses = setOf( MockNetworkStatusFactory.createVerified(ethNetwork), MockNetworkStatusFactory.createVerified(cardanoNetwork), ) - val networksStatusesFlow = flowOf(statuses.map(NetworkStatus::toSimple).toSet()) + val simpleStatuses = statuses.map(NetworkStatus::toSimple).toSet() + + val networksStatusesFlow = flowOf(simpleStatuses) every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet - val actual = producer.produce() + every { + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.first().network - // check after producer.produce() - verify { networksStatusesStore.get(params.userWalletId) } + every { + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.last().network - val values = getEmittedValues(flow = actual) + // Act + val actual = producer.produce().let(::getEmittedValues) - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } + // Assert + val expected = statuses - Truth.assertThat(values.size).isEqualTo(1) - Truth.assertThat(values.first()).isEqualTo(statuses) + Truth.assertThat(actual.size).isEqualTo(1) + Truth.assertThat(actual.first()).isEqualTo(expected) + + verifyOrder { + networksStatusesStore.get(params.userWalletId) + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } } @Test - fun `test that flow is updated if statuses are updated`() = runTest { + fun `flow will updated if statuses are updated`() = runTest { + // Arrange val networksStatusesFlow = MutableSharedFlow>(replay = 2) - every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow - every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet - - val actual = producer.produce() - - // check after producer.produce() - verify { networksStatusesStore.get(params.userWalletId) } - - // first emit val statuses = setOf( MockNetworkStatusFactory.createUnreachable(ethNetwork), MockNetworkStatusFactory.createUnreachable(cardanoNetwork), ) - networksStatusesFlow.emit(statuses.map(NetworkStatus::toSimple).toSet()) + val simpleStatuses = statuses.map(NetworkStatus::toSimple).toSet() - val values1 = getEmittedValues(flow = actual) - - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } - - Truth.assertThat(values1.size).isEqualTo(1) - Truth.assertThat(values1.first()).isEqualTo(statuses) - - // second emit val updatedStatuses = setOf( MockNetworkStatusFactory.createVerified(ethNetwork), MockNetworkStatusFactory.createVerified(cardanoNetwork), ) - networksStatusesFlow.emit(updatedStatuses.map(NetworkStatus::toSimple).toSet()) + val updatedSimpleStatuses = updatedStatuses.map(NetworkStatus::toSimple).toSet() - val values2 = getEmittedValues(flow = actual) + // region every + every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow + every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet + every { + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.first().network - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } + every { + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.last().network - val expected = listOf(statuses, updatedStatuses) - Truth.assertThat(values2.size).isEqualTo(2) - Truth.assertThat(values2).isEqualTo(expected) + every { + networkFactory.create( + networkId = updatedSimpleStatuses.first().id, + derivationPath = updatedSimpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns updatedStatuses.first().network + + every { + networkFactory.create( + networkId = updatedSimpleStatuses.last().id, + derivationPath = updatedSimpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns updatedStatuses.last().network + // endregion + + val producerFlow = producer.produce() + + // Act 1 (first emit) + networksStatusesFlow.emit(simpleStatuses) + + val actual1 = getEmittedValues(flow = producerFlow) + + // Assert + val expected1 = statuses + + Truth.assertThat(actual1.size).isEqualTo(1) + Truth.assertThat(actual1.first()).isEqualTo(expected1) + + verifyOrder { + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } + + // Act 2 (second emit) + networksStatusesFlow.emit(updatedSimpleStatuses) + + val actual2 = getEmittedValues(flow = producerFlow) + + // Assert + val expected2 = listOf(statuses, updatedStatuses) + + Truth.assertThat(actual2.size).isEqualTo(2) + Truth.assertThat(actual2).isEqualTo(expected2) + + verifyOrder { + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = updatedSimpleStatuses.first().id, + derivationPath = updatedSimpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = updatedSimpleStatuses.last().id, + derivationPath = updatedSimpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } } @Test - fun `test that flow is filtered the same status`() = runTest { + fun `flow is filtered the same status`() = runTest { + // Arrange val networksStatusesFlow = MutableSharedFlow>(replay = 2) + val statuses = setOf( + MockNetworkStatusFactory.createUnreachable(ethNetwork), + MockNetworkStatusFactory.createUnreachable(cardanoNetwork), + ) + + val simpleStatuses = statuses.map(NetworkStatus::toSimple).toSet() + + // region every every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet - val actual = producer.produce() + every { + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.first().network - // check after producer.produce() - verify { networksStatusesStore.get(params.userWalletId) } + every { + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.last().network + // endregion - // first emit - val statuses = setOf( - MockNetworkStatusFactory.createUnreachable(ethNetwork), - MockNetworkStatusFactory.createUnreachable(cardanoNetwork), - ) + val producerFlow = producer.produce() - networksStatusesFlow.emit(statuses.map(NetworkStatus::toSimple).toSet()) + // Act 1 (first emit) + networksStatusesFlow.emit(simpleStatuses) - val values1 = getEmittedValues(flow = actual) + val actual1 = getEmittedValues(flow = producerFlow) - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } + // Assert + val expected1 = statuses - Truth.assertThat(values1.size).isEqualTo(1) - Truth.assertThat(values1.first()).isEqualTo(statuses) + Truth.assertThat(actual1.size).isEqualTo(1) + Truth.assertThat(actual1.first()).isEqualTo(expected1) - // second emit - networksStatusesFlow.emit(statuses.map(NetworkStatus::toSimple).toSet()) + verifyOrder { + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } - val values2 = getEmittedValues(flow = actual) + // Act 2 (second emit) + networksStatusesFlow.emit(simpleStatuses) - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } + val actual2 = getEmittedValues(flow = producerFlow) - Truth.assertThat(values2.size).isEqualTo(1) - Truth.assertThat(values2.first()).isEqualTo(statuses) + // Asset + val expected2 = expected1 + Truth.assertThat(actual2.size).isEqualTo(1) + Truth.assertThat(actual2.first()).isEqualTo(expected2) } @Test - fun `test if flow throws exception`() = runTest { + fun `flow throws exception`() = runTest { + // Arrange val exception = IllegalStateException() + val statuses = setOf( MockNetworkStatusFactory.createUnreachable(ethNetwork), MockNetworkStatusFactory.createUnreachable(cardanoNetwork), ) + val simpleStatuses = statuses.map(NetworkStatus::toSimple).toSet() + val innerFlow = MutableStateFlow(value = false) val networksStatusesFlow = flow { if (innerFlow.value) { - emit(statuses.map(NetworkStatus::toSimple).toSet()) + emit(simpleStatuses) } else { throw exception } } .buffer(capacity = 5) + // region every every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet + every { + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.first().network + every { + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } returns statuses.last().network + // endregion - val actual = producer.produceWithFallback() + val producerFlow = producer.produceWithFallback() - // check after producer.produce() - verify { networksStatusesStore.get(params.userWalletId) } + // Act 1 (fallback) + val actual1 = getEmittedValues(flow = producerFlow) - val values1 = getEmittedValues(flow = actual) + // Assert + val expected1 = emptySet() + Truth.assertThat(actual1.size).isEqualTo(1) + Truth.assertThat(actual1.first()).isEqualTo(expected1) - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify(inverse = true) { userWalletsStore.getSyncOrNull(params.userWalletId) } - - Truth.assertThat(values1.size).isEqualTo(1) - Truth.assertThat(values1).isEqualTo(listOf(emptySet())) + verifyOrder(inverse = true) { + userWalletsStore.getSyncOrNull(any()) + networkFactory.create(networkId = any(), derivationPath = any(), scanResponse = any()) + } + // Act 2 (emit) innerFlow.emit(value = true) + val actual2 = getEmittedValues(flow = producerFlow) - val values2 = getEmittedValues(flow = actual) + // Assert + val expected2 = statuses + Truth.assertThat(actual2.size).isEqualTo(1) + Truth.assertThat(actual2.first()).isEqualTo(expected2) - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } - - Truth.assertThat(values2.size).isEqualTo(1) - Truth.assertThat(values2).isEqualTo(listOf(statuses)) + verifyOrder { + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } } @Test - fun `test that flow is empty`() = runTest { + fun `flow is empty if store returns empty flow`() = runTest { + // Arrange every { networksStatusesStore.get(params.userWalletId) } returns emptyFlow() - every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet - val actual = producer.produce() + // Act + val actual = producer.produce().let(::getEmittedValues) + + // Assert + val expected = emptySet() + Truth.assertThat(actual.size).isEqualTo(1) + Truth.assertThat(actual.first()).isEqualTo(expected) - // check after producer.produce() verify { networksStatusesStore.get(params.userWalletId) } - - val values = getEmittedValues(flow = actual) - verify(inverse = true) { userWalletsStore.getSyncOrNull(params.userWalletId) } - - Truth.assertThat(values.size).isEqualTo(1) - Truth.assertThat(values).isEqualTo(listOf(emptySet())) } @Test - fun `test if network is null`() = runTest { + fun `flow returns empty list if networkFactory returns null`() = runTest { + // Arrange val statuses = setOf( MockNetworkStatusFactory.createVerified(ethNetwork), MockNetworkStatusFactory.createVerified(cardanoNetwork), ) - val networksStatusesFlow = flowOf(statuses.map(NetworkStatus::toSimple).toSet()) + val simpleStatuses = statuses.map(NetworkStatus::toSimple).toSet() + + val networksStatusesFlow = flowOf(simpleStatuses) every { networksStatusesStore.get(params.userWalletId) } returns networksStatusesFlow every { userWalletsStore.getSyncOrNull(params.userWalletId) } returns userWallet - every { excludedBlockchains.contains(any()) } returns true // cause network is null + coEvery { networkFactory.create(networkId = any(), any(), any()) } returns null - val actual = producer.produce() + // Act + val actual = producer.produce().let(::getEmittedValues) - // check after producer.produce() - verify { networksStatusesStore.get(params.userWalletId) } + // Assert + val expected = emptySet() + Truth.assertThat(actual.size).isEqualTo(1) + Truth.assertThat(actual.first()).isEqualTo(expected) - val values = getEmittedValues(flow = actual) - - // Check after flow was observed by subscriber (getEmittedValues). - // Otherwise, userWalletsStore.getSyncOrNull is not called. - verify { userWalletsStore.getSyncOrNull(params.userWalletId) } - - Truth.assertThat(values.size).isEqualTo(1) - Truth.assertThat(values).isEqualTo(listOf(emptySet())) + verifyOrder { + networksStatusesStore.get(params.userWalletId) + userWalletsStore.getSyncOrNull(params.userWalletId) + networkFactory.create( + networkId = simpleStatuses.first().id, + derivationPath = simpleStatuses.first().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + networkFactory.create( + networkId = simpleStatuses.last().id, + derivationPath = simpleStatuses.last().id.derivationPath, + scanResponse = userWallet.scanResponse, + ) + } } private companion object { diff --git a/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt b/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt index b94dada0ca..be8119323d 100644 --- a/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt +++ b/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.nft.NFTPersistenceStore import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory import com.tangem.datasource.local.nft.NFTRuntimeStore @@ -46,9 +46,10 @@ internal class DefaultNFTRepository @Inject constructor( private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory, private val walletManagersFacade: WalletManagersFacade, private val dispatchers: CoroutineDispatcherProvider, - private val excludedBlockchains: ExcludedBlockchains, private val userWalletsStore: UserWalletsStore, private val nftFeatureToggles: NFTFeatureToggles, + private val networkFactory: NetworkFactory, + excludedBlockchains: ExcludedBlockchains, resources: Resources, ) : NFTRepository { @@ -200,11 +201,10 @@ internal class DefaultNFTRepository @Inject constructor( .entries .filter { it.canHandleNFTs() && !it.isTestnet() } .mapNotNull { - getNetwork( + networkFactory.create( blockchain = it, extraDerivationPath = null, scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) } } diff --git a/data/nft/src/main/kotlin/com/tangem/data/nft/di/NFTDataModule.kt b/data/nft/src/main/kotlin/com/tangem/data/nft/di/NFTDataModule.kt index e66df70d44..d91a537605 100644 --- a/data/nft/src/main/kotlin/com/tangem/data/nft/di/NFTDataModule.kt +++ b/data/nft/src/main/kotlin/com/tangem/data/nft/di/NFTDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.nft.di import android.content.Context import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.nft.DefaultNFTRepository import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory @@ -32,6 +33,7 @@ internal object NFTDataModule { excludedBlockchains: ExcludedBlockchains, userWalletsStore: UserWalletsStore, nftFeatureToggles: NFTFeatureToggles, + networkFactory: NetworkFactory, ): NFTRepository = DefaultNFTRepository( nftPersistenceStoreFactory = nftPersistenceStoreFactory, nftRuntimeStoreFactory = nftRuntimeStoreFactory, @@ -40,6 +42,7 @@ internal object NFTDataModule { excludedBlockchains = excludedBlockchains, userWalletsStore = userWalletsStore, nftFeatureToggles = nftFeatureToggles, + networkFactory = networkFactory, resources = context.resources, ) } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt index 5a129e0c63..a0cadbe035 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency @@ -20,17 +20,18 @@ import java.math.BigDecimal * * @property scanResponse scan response * @property imageHost image host - * @property excludedBlockchains excluded blockchains + * @param excludedBlockchains excluded blockchains * [REDACTED_AUTHOR] */ internal class HotCryptoCurrencyConverter( private val scanResponse: ScanResponse, private val imageHost: String?, - private val excludedBlockchains: ExcludedBlockchains, + excludedBlockchains: ExcludedBlockchains, ) : Converter { - private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) } + private val cryptoCurrencyFactory by lazy(LazyThreadSafetyMode.NONE) { CryptoCurrencyFactory(excludedBlockchains) } + private val networkFactory by lazy(LazyThreadSafetyMode.NONE) { NetworkFactory(excludedBlockchains) } override fun convert(value: HotCryptoResponse.Token): HotCryptoCurrency? { val rawId = value.id?.let(CryptoCurrency::RawID) ?: return null @@ -77,11 +78,10 @@ internal class HotCryptoCurrencyConverter( private fun createNetwork(networkId: String): Network? { val blockchain = Blockchain.fromNetworkId(networkId) ?: return null - return getNetwork( + return networkFactory.create( blockchain = blockchain, extraDerivationPath = null, scanResponse = scanResponse, - excludedBlockchains = excludedBlockchains, ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt index bb1f318a62..eb27b1bbf1 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt @@ -1,9 +1,8 @@ package com.tangem.data.visa.utils import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkAddress @@ -19,7 +18,7 @@ import javax.inject.Inject internal class VisaCurrencyFactory @Inject constructor( private val cryptoCurrencyFactory: CryptoCurrencyFactory, - private val excludedBlockchains: ExcludedBlockchains, + private val networkFactory: NetworkFactory, ) { fun create(userWallet: UserWallet, contractInfo: VisaContractInfo, fiatRate: BigDecimal): VisaCurrency { @@ -31,11 +30,10 @@ internal class VisaCurrencyFactory @Inject constructor( } val remainingOtpLimit = getRemainingOtp(currentLimit, now) - val currencyNetwork = getNetwork( + val currencyNetwork = networkFactory.create( blockchain = Blockchain.Polygon, extraDerivationPath = null, derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, - excludedBlockchains = excludedBlockchains, canHandleTokens = true, ) ?: error("Unable to create network for Visa currency") diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt index 4ee8f349fc..2dba0c115b 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNamespaceConverter.kt @@ -2,7 +2,7 @@ package com.tangem.data.walletconnect.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey import com.tangem.domain.models.network.Network @@ -21,11 +21,11 @@ internal interface WcNamespaceConverter { fun toNetwork(chainId: String, wallet: UserWallet): Network? fun toNetwork(chainId: String, wallet: UserWallet, excludedBlockchains: ExcludedBlockchains): Network? { val blockchain = toBlockchain(chainId) ?: return null - return getNetwork( + + return NetworkFactory(excludedBlockchains).create( blockchain = blockchain, extraDerivationPath = null, scanResponse = wallet.scanResponse, - excludedBlockchains = excludedBlockchains, ) } } \ No newline at end of file