Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-22 16:04:01 +04:00
parent a667fcc955
commit db6157cae4
20 changed files with 435 additions and 500 deletions

View file

@ -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,
)
}
}

View file

@ -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<ByteArrayKey, ExtendedPublicKeysMap>
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,
)
},
)

View file

@ -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")

View file

@ -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(

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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,
)
}
}

View file

@ -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,

View file

@ -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<List<TokenMarketExchangesResponse.Exchange>>,
private val maxApyStore: RuntimeStateStore<BigDecimal?>,
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(

View file

@ -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,
)
}
}

View file

@ -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<Set<NetworkStatus>> {
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)

View file

@ -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<NetworksStatusesStore>()
private val userWalletsStore = mockk<UserWalletsStore>()
private val excludedBlockchains = mockk<ExcludedBlockchains>()
private val networkFactory = mockk<NetworkFactory>()
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<Set<SimpleNetworkStatus>>(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<Set<SimpleNetworkStatus>>(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()
// check after producer.produce()
verify { networksStatusesStore.get(params.userWalletId) }
// first emit
val statuses = setOf(
MockNetworkStatusFactory.createUnreachable(ethNetwork),
MockNetworkStatusFactory.createUnreachable(cardanoNetwork),
every {
networkFactory.create(
networkId = simpleStatuses.first().id,
derivationPath = simpleStatuses.first().id.derivationPath,
scanResponse = userWallet.scanResponse,
)
} returns statuses.first().network
networksStatusesFlow.emit(statuses.map(NetworkStatus::toSimple).toSet())
every {
networkFactory.create(
networkId = simpleStatuses.last().id,
derivationPath = simpleStatuses.last().id.derivationPath,
scanResponse = userWallet.scanResponse,
)
} returns statuses.last().network
// endregion
val values1 = getEmittedValues(flow = actual)
val producerFlow = producer.produce()
// Check after flow was observed by subscriber (getEmittedValues).
// Otherwise, userWalletsStore.getSyncOrNull is not called.
verify { userWalletsStore.getSyncOrNull(params.userWalletId) }
// Act 1 (first emit)
networksStatusesFlow.emit(simpleStatuses)
Truth.assertThat(values1.size).isEqualTo(1)
Truth.assertThat(values1.first()).isEqualTo(statuses)
val actual1 = getEmittedValues(flow = producerFlow)
// second emit
networksStatusesFlow.emit(statuses.map(NetworkStatus::toSimple).toSet())
// Assert
val expected1 = statuses
val values2 = getEmittedValues(flow = actual)
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 { userWalletsStore.getSyncOrNull(params.userWalletId) }
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,
)
}
Truth.assertThat(values2.size).isEqualTo(1)
Truth.assertThat(values2.first()).isEqualTo(statuses)
// Act 2 (second emit)
networksStatusesFlow.emit(simpleStatuses)
val actual2 = getEmittedValues(flow = producerFlow)
// 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<NetworkStatus>()
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<NetworkStatus>()))
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<NetworkStatus>()
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<NetworkStatus>()))
}
@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<NetworkStatus>()
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<NetworkStatus>()))
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 {

View file

@ -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,
)
}
}

View file

@ -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,
)
}

View file

@ -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<HotCryptoResponse.Token, HotCryptoCurrency?> {
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,
)
}

View file

@ -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")

View file

@ -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,
)
}
}