Updated on 2026-08-14
This commit is contained in:
commit
1146554992
1461 changed files with 62266 additions and 17096 deletions
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
|
||||
// region Project - Data
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.data.dynamicAddresses)
|
||||
// endregion
|
||||
|
||||
// region Project - Domain
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:CommonNetworkStatusFetcher.kt$CommonNetworkStatusFetcher${ Timber.e("Failed to fetch network status for $userWalletId [${network.rawId}]: $it") networksStatusesStore.setSourceAsOnlyCache(userWalletId = userWalletId, network = network) }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultMultiNetworkStatusFetcher.kt$DefaultMultiNetworkStatusFetcher${ networksStatusesStore.setSourceAsOnlyCache( userWalletId = params.userWalletId, networks = params.networks, ) raise(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultNetworksRepository.kt$DefaultNetworksRepository${ Timber.e(it, "Unable to create wallet currencies") return emptyList() }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultNetworksRepository.kt$DefaultNetworksRepository${ Timber.e(it, "Unable to create wallet currencies") return@withContext }</ID>
|
||||
<ID>MultilineLambdaItParameter:NetworkAmountsConverter.kt$NetworkAmountsConverter${ val amount = it.value as? NetworkStatus.Amount.Loaded ?: return@mapNotNull null CurrencyAmount( id = currencyIdConverter.convertBack(value = it.key), amount = amount.value, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NetworkAmountsConverter.kt$NetworkAmountsConverter${ val currencyId = currencyIdConverter.convert(value = it.id) val amount = NetworkStatus.Amount.Loaded(value = it.amount) currencyId to amount }</ID>
|
||||
<ID>MultilineLambdaItParameter:NetworkStatusSupplierModule.kt$NetworkStatusSupplierModule.<no name provided>${ "single_network_status_${it.userWalletId.stringValue}_${it.network.rawId}_" + it.network.derivationPath.value }</ID>
|
||||
<ID>MultilineLambdaItParameter:NetworkYieldSupplyStatusConverter.kt$NetworkYieldSupplyStatusConverter${ val id = currencyIdConverter.convert(value = it.id) val status = YieldSupplyStatus( isActive = it.isActive, isInitialized = it.isInitialized, isAllowedToSpend = it.isAllowedToSpend, effectiveProtocolBalance = it.effectiveProtocolBalance, ) id to status }</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:DefaultNetworksRepository.kt$DefaultNetworksRepository$runCatching</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -12,33 +12,33 @@ private typealias AmountsDomainModel = Map<CryptoCurrency.ID, NetworkStatus.Amou
|
|||
/**
|
||||
* Converter from [AmountsDataModel] to [AmountsDomainModel] and vice versa
|
||||
*
|
||||
* @param rawNetworkId the raw network ID associated with the currency
|
||||
* @param blockchainId the blockchain ID associated with the network (e.g. `Blockchain.id`)
|
||||
* @param derivationPath the derivation path used for the network
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworkAmountsConverter(
|
||||
rawNetworkId: String,
|
||||
blockchainId: String,
|
||||
derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<AmountsDataModel, AmountsDomainModel> {
|
||||
|
||||
private val currencyIdConverter = CurrencyIdConverter(rawNetworkId, derivationPath)
|
||||
private val currencyIdConverter = NetworkCurrencyIdConverter(blockchainId, derivationPath)
|
||||
|
||||
override fun convert(value: AmountsDataModel): AmountsDomainModel {
|
||||
return value.associate {
|
||||
val currencyId = currencyIdConverter.convert(value = it.id)
|
||||
val amount = NetworkStatus.Amount.Loaded(value = it.amount)
|
||||
return value.associate { currencyAmount ->
|
||||
val currencyId = currencyIdConverter.convert(value = currencyAmount.id)
|
||||
val amount = NetworkStatus.Amount.Loaded(value = currencyAmount.amount)
|
||||
|
||||
currencyId to amount
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: AmountsDomainModel): AmountsDataModel {
|
||||
return value.mapNotNull {
|
||||
val amount = it.value as? NetworkStatus.Amount.Loaded ?: return@mapNotNull null
|
||||
return value.mapNotNull { (currencyId, networkAmount) ->
|
||||
val amount = networkAmount as? NetworkStatus.Amount.Loaded ?: return@mapNotNull null
|
||||
|
||||
CurrencyAmount(
|
||||
id = currencyIdConverter.convertBack(value = it.key),
|
||||
id = currencyIdConverter.convertBack(value = currencyId),
|
||||
amount = amount.value,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.CurrencyId.Companion.CONTRACT_ADDRESS_DELIMITER
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -12,16 +14,22 @@ import com.tangem.domain.models.currency.CryptoCurrency.ID.Suffix as CurrencyIdS
|
|||
/**
|
||||
* Converts between [CurrencyId] and [CryptoCurrency.ID].
|
||||
*
|
||||
* @property rawNetworkId the raw network ID associated with the currency
|
||||
* @property blockchainId the blockchain ID associated with the currency
|
||||
* @property derivationPath the derivation path used for the network
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class CurrencyIdConverter(
|
||||
private val rawNetworkId: String,
|
||||
internal class NetworkCurrencyIdConverter(
|
||||
private val blockchainId: String,
|
||||
private val derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<CurrencyId, CryptoCurrency.ID> {
|
||||
|
||||
// Cache stores blockchainId in legacy format (e.g. "BTC"), but runtime
|
||||
// CryptoCurrency.ID expects the new network rawId (e.g. "bitcoin") matching
|
||||
// Network.rawId built from Blockchain.toNetworkId(). Convert once on construction
|
||||
// so that IDs reconstructed from cache match those built at runtime.
|
||||
private val networkRawId: String = Blockchain.fromId(blockchainId).toNetworkId()
|
||||
|
||||
override fun convert(value: CurrencyId): CryptoCurrency.ID {
|
||||
val suffixParts = value.value.split(CONTRACT_ADDRESS_DELIMITER)
|
||||
|
||||
|
|
@ -31,7 +39,7 @@ internal class CurrencyIdConverter(
|
|||
return if (contractAddress.isNullOrBlank()) {
|
||||
getCoinId(
|
||||
coinId = rawId.takeUnless { it.isNullOrBlank() }
|
||||
?: error("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
?: error("Coin id is null for $blockchainId with $derivationPath"),
|
||||
)
|
||||
} else {
|
||||
getTokenId(
|
||||
|
|
@ -43,14 +51,12 @@ internal class CurrencyIdConverter(
|
|||
|
||||
override fun convertBack(value: CryptoCurrency.ID): CurrencyId {
|
||||
return if (value.isCoin) {
|
||||
CurrencyId.createCoinId(
|
||||
coinId = Blockchain.fromId(value.rawNetworkId).toCoinId(),
|
||||
)
|
||||
CurrencyId.createCoinId(coinId = value.toBlockchain().toCoinId())
|
||||
} else {
|
||||
CurrencyId.createTokenId(
|
||||
rawTokenId = value.rawCurrencyId?.value,
|
||||
contractAddress = requireNotNull(value.contractAddress) {
|
||||
"Token contractAddress is null for token id: $this"
|
||||
"Token contractAddress is null for token id: $value"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -82,17 +88,17 @@ internal class CurrencyIdConverter(
|
|||
return when (derivationPath) {
|
||||
is Network.DerivationPath.Card -> {
|
||||
CryptoCurrency.ID.Body.NetworkIdWithDerivationPath(
|
||||
rawId = rawNetworkId,
|
||||
rawId = networkRawId,
|
||||
derivationPath = derivationPath.value,
|
||||
)
|
||||
}
|
||||
is Network.DerivationPath.Custom -> {
|
||||
CryptoCurrency.ID.Body.NetworkIdWithDerivationPath(
|
||||
rawId = rawNetworkId,
|
||||
rawId = networkRawId,
|
||||
derivationPath = derivationPath.value,
|
||||
)
|
||||
}
|
||||
is Network.DerivationPath.None -> CryptoCurrency.ID.Body.NetworkId(rawNetworkId)
|
||||
is Network.DerivationPath.None -> CryptoCurrency.ID.Body.NetworkId(networkRawId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -15,17 +16,18 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
|
|||
return when (val status = value.value) {
|
||||
is NetworkStatus.Verified -> {
|
||||
val address = NetworkAddressConverter.convertBack(value = status.address)
|
||||
val blockchainId = value.network.toBlockchain().id
|
||||
val amountsConverter = NetworkAmountsConverter(
|
||||
rawNetworkId = value.network.rawId,
|
||||
blockchainId = blockchainId,
|
||||
derivationPath = value.network.derivationPath,
|
||||
)
|
||||
val yieldSupplyStatusConverter = NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId = value.network.rawId,
|
||||
blockchainId = blockchainId,
|
||||
derivationPath = value.network.derivationPath,
|
||||
)
|
||||
|
||||
NetworkStatusDM.Verified(
|
||||
networkId = NetworkStatusDM.ID(value = value.network.rawId),
|
||||
networkId = NetworkStatusDM.ID(value = blockchainId),
|
||||
derivationPath = NetworkDerivationPathConverter.convertBack(value = value.network.derivationPath),
|
||||
selectedAddress = address.selectedAddress,
|
||||
availableAddresses = address.addresses,
|
||||
|
|
@ -35,9 +37,10 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
|
|||
}
|
||||
is NetworkStatus.NoAccount -> {
|
||||
val address = NetworkAddressConverter.convertBack(value = status.address)
|
||||
val blockchainId = value.network.toBlockchain().id
|
||||
|
||||
NetworkStatusDM.NoAccount(
|
||||
networkId = NetworkStatusDM.ID(value = value.network.rawId),
|
||||
networkId = NetworkStatusDM.ID(value = blockchainId),
|
||||
derivationPath = NetworkDerivationPathConverter.convertBack(value = value.network.derivationPath),
|
||||
selectedAddress = address.selectedAddress,
|
||||
availableAddresses = address.addresses,
|
||||
|
|
|
|||
|
|
@ -10,20 +10,20 @@ private typealias YieldSupplyStatusDataModel = List<NetworkStatusDM.YieldSupplyS
|
|||
private typealias YieldSupplyStatusDomainModel = Map<CryptoCurrency.ID, YieldSupplyStatus?>
|
||||
|
||||
internal class NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId: String,
|
||||
blockchainId: String,
|
||||
derivationPath: Network.DerivationPath,
|
||||
) : TwoWayConverter<YieldSupplyStatusDataModel, YieldSupplyStatusDomainModel> {
|
||||
|
||||
private val currencyIdConverter = CurrencyIdConverter(rawNetworkId, derivationPath)
|
||||
private val currencyIdConverter = NetworkCurrencyIdConverter(blockchainId, derivationPath)
|
||||
|
||||
override fun convert(value: YieldSupplyStatusDataModel): YieldSupplyStatusDomainModel {
|
||||
return value.associate {
|
||||
val id = currencyIdConverter.convert(value = it.id)
|
||||
return value.associate { yieldSupplyStatus ->
|
||||
val id = currencyIdConverter.convert(value = yieldSupplyStatus.id)
|
||||
val status = YieldSupplyStatus(
|
||||
isActive = it.isActive,
|
||||
isInitialized = it.isInitialized,
|
||||
isAllowedToSpend = it.isAllowedToSpend,
|
||||
effectiveProtocolBalance = it.effectiveProtocolBalance,
|
||||
isActive = yieldSupplyStatus.isActive,
|
||||
isInitialized = yieldSupplyStatus.isInitialized,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
effectiveProtocolBalance = yieldSupplyStatus.effectiveProtocolBalance,
|
||||
)
|
||||
|
||||
id to status
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -23,14 +25,15 @@ internal object SimpleNetworkStatusConverter : Converter<NetworkStatusDM, Simple
|
|||
)
|
||||
|
||||
val derivationPath = NetworkDerivationPathConverter.convert(value = value.derivationPath)
|
||||
val blockchainId = value.networkId.value
|
||||
|
||||
val amountsConverter = NetworkAmountsConverter(
|
||||
rawNetworkId = value.networkId.value,
|
||||
blockchainId = blockchainId,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
val yieldSupplyStatusConverter = NetworkYieldSupplyStatusConverter(
|
||||
rawNetworkId = value.networkId.value,
|
||||
blockchainId = blockchainId,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
|
|
@ -56,7 +59,7 @@ internal object SimpleNetworkStatusConverter : Converter<NetworkStatusDM, Simple
|
|||
|
||||
return SimpleNetworkStatus(
|
||||
id = Network.ID(
|
||||
value = value.networkId.value,
|
||||
value = Blockchain.fromId(blockchainId).toNetworkId(),
|
||||
derivationPath = NetworkDerivationPathConverter.convert(value = value.derivationPath),
|
||||
),
|
||||
value = status,
|
||||
|
|
|
|||
|
|
@ -17,21 +17,26 @@ internal object NetworkStatusSupplierModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideSingleNetworkStatusSupplier(factory: SingleNetworkStatusProducer.Factory): SingleNetworkStatusSupplier {
|
||||
return object : SingleNetworkStatusSupplier(
|
||||
return SingleNetworkStatusSupplier(
|
||||
factory = factory,
|
||||
keyCreator = {
|
||||
"single_network_status_${it.userWalletId.stringValue}_${it.network.rawId}_" +
|
||||
it.network.derivationPath.value
|
||||
keyCreator = { params ->
|
||||
listOf(
|
||||
"single_network_status",
|
||||
params.userWalletId.stringValue,
|
||||
params.network.rawId,
|
||||
params.network.derivationPath.value,
|
||||
)
|
||||
.joinToString(separator = "_")
|
||||
},
|
||||
) {}
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMultiNetworkStatusSupplier(factory: MultiNetworkStatusProducer.Factory): MultiNetworkStatusSupplier {
|
||||
return object : MultiNetworkStatusSupplier(
|
||||
return MultiNetworkStatusSupplier(
|
||||
factory = factory,
|
||||
keyCreator = { "multi_networks_statuses_${it.userWalletId.stringValue}" },
|
||||
) {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ internal class CommonNetworkStatusFetcher @Inject constructor(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
networkCurrencies: Set<CryptoCurrency>,
|
||||
xpub: String? = null,
|
||||
): Either<Throwable, Unit> {
|
||||
// Guard: empty networkCurrencies would result in NetworkStatus.Verified(amounts=emptyMap()),
|
||||
// which overwrites any valid cached status and leaves all currencies in this network as Loading.
|
||||
|
|
@ -60,6 +61,7 @@ internal class CommonNetworkStatusFetcher @Inject constructor(
|
|||
extraTokens = networkCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.toSet(),
|
||||
xpub = xpub,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ package com.tangem.data.networks.multi
|
|||
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.data.dynamicaddresses.DynamicAddressesInitializer
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.domain.core.utils.eitherOn
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
@ -27,11 +29,11 @@ import javax.inject.Inject
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
||||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
|
||||
private val dynamicAddressesInitializer: DynamicAddressesInitializer,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiNetworkStatusFetcher {
|
||||
|
||||
|
|
@ -40,13 +42,21 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
|
||||
val networksCurrencies = catch(
|
||||
block = { createNetworksCurrenciesMap(params) },
|
||||
catch = {
|
||||
catch = { error ->
|
||||
networksStatusesStore.setSourceAsOnlyCache(
|
||||
userWalletId = params.userWalletId,
|
||||
networks = params.networks,
|
||||
)
|
||||
|
||||
raise(it)
|
||||
raise(error)
|
||||
},
|
||||
)
|
||||
|
||||
val xpubByNetwork = catch(
|
||||
block = { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) },
|
||||
catch = { error ->
|
||||
TangemLogger.e("Failed to build XPUBs for restore", error)
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -58,6 +68,7 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
network = network,
|
||||
networkCurrencies = networksCurrencies[network].orEmpty().toSet(),
|
||||
xpub = xpubByNetwork[network],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.networks.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -33,13 +34,12 @@ internal class DefaultNetworksRepository(
|
|||
|
||||
override suspend fun fetchPendingTransactions(userWalletId: UserWalletId, network: Network) {
|
||||
withContext(dispatchers.default) {
|
||||
val currencies = runCatching {
|
||||
val currencies = runSuspendCatching {
|
||||
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
|
||||
}.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return@withContext
|
||||
}
|
||||
.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
fetchPendingTransactions(userWalletId = userWalletId, network = network, currencies = currencies)
|
||||
}
|
||||
|
|
@ -49,34 +49,34 @@ internal class DefaultNetworksRepository(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): List<CryptoCurrencyAddress> {
|
||||
return runCatching { cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network) }
|
||||
.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return emptyList()
|
||||
}
|
||||
.map { currency ->
|
||||
CryptoCurrencyAddress(
|
||||
cryptoCurrency = currency,
|
||||
address = getDefaultAddress(userWalletId, network).orEmpty(),
|
||||
)
|
||||
}
|
||||
return runSuspendCatching {
|
||||
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
|
||||
}.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return emptyList()
|
||||
}.map { currency ->
|
||||
CryptoCurrencyAddress(
|
||||
cryptoCurrency = currency,
|
||||
address = getDefaultAddress(userWalletId, network).orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getNetworkAddresses(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network.RawID,
|
||||
): List<CryptoCurrencyAddress> {
|
||||
return runCatching { cardCryptoCurrencyFactory.createByRawId(userWalletId = userWalletId, network = network) }
|
||||
.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return emptyList()
|
||||
}
|
||||
.map { currency ->
|
||||
CryptoCurrencyAddress(
|
||||
cryptoCurrency = currency,
|
||||
address = getDefaultAddress(userWalletId, currency.network).orEmpty(),
|
||||
)
|
||||
}
|
||||
return runSuspendCatching {
|
||||
cardCryptoCurrencyFactory.createByRawId(userWalletId = userWalletId, network = network)
|
||||
}.getOrElse { error ->
|
||||
TangemLogger.e("Unable to create wallet currencies", error)
|
||||
return emptyList()
|
||||
}.map { currency ->
|
||||
CryptoCurrencyAddress(
|
||||
cryptoCurrency = currency,
|
||||
address = getDefaultAddress(userWalletId, currency.network).orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String? {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.networks.store
|
|||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.networks.converters.NetworkStatusDataModelConverter
|
||||
import com.tangem.data.networks.converters.SimpleNetworkStatusConverter
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
|
|
@ -27,15 +28,15 @@ internal typealias WalletIdWithStatusDM = Map<String, Set<NetworkStatusDM>>
|
|||
* Default implementation of [NetworksStatusesStore]
|
||||
*
|
||||
* @param context context
|
||||
* @param scope app coroutine scope
|
||||
* @property runtimeStore runtime store
|
||||
* @property persistenceDataStore persistence store
|
||||
* @param dispatchers dispatchers
|
||||
*/
|
||||
internal class DefaultNetworksStatusesStore(
|
||||
context: Context,
|
||||
scope: AppCoroutineScope,
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithSimpleStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithStatusDM>,
|
||||
private val scope: AppCoroutineScope,
|
||||
) : NetworksStatusesStore {
|
||||
|
||||
init {
|
||||
|
|
@ -112,7 +113,8 @@ internal class DefaultNetworksStatusesStore(
|
|||
storedStatuses.toMutableMap().apply {
|
||||
val updatedValues = this[userWalletId.stringValue].orEmpty().filterNot {
|
||||
networks.any { network ->
|
||||
it.networkId.value == network.rawId && it.derivationPath.value == network.derivationPath.value
|
||||
it.networkId.value == network.toBlockchain().id &&
|
||||
it.derivationPath.value == network.derivationPath.value
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@ import java.math.BigDecimal
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkAmountsConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
// Cache stores the SDK-level Blockchain.id (legacy format, e.g. "ETH").
|
||||
// The converter normalizes it to the canonical network rawId ("ethereum") via
|
||||
// Blockchain.fromId(...).toNetworkId() so that resulting CryptoCurrency.IDs match those
|
||||
// built at runtime from Network.rawId.
|
||||
private val blockchainId = "ETH"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = NetworkAmountsConverter(rawNetworkId = rawNetworkId, derivationPath = derivationPath)
|
||||
private val converter = NetworkAmountsConverter(blockchainId = blockchainId, derivationPath = derivationPath)
|
||||
|
||||
@Test
|
||||
fun convert() {
|
||||
|
|
@ -47,12 +51,12 @@ internal class NetworkAmountsConverterTest {
|
|||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue("coin⟨ethereum→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.TEN),
|
||||
)
|
||||
|
||||
|
|
@ -63,12 +67,12 @@ internal class NetworkAmountsConverterTest {
|
|||
fun convertBack() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue("coin⟨ethereum→$derivationPathHashCode⟩ethereum") to Loaded(value = BigDecimal.ONE),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
) to Loaded(value = BigDecimal.TEN),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
|
|
@ -13,12 +14,16 @@ import org.junit.jupiter.params.ParameterizedTest
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CurrencyIdConverterTest {
|
||||
class NetworkCurrencyIdConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
// Legacy SDK format stored in cache (see NetworkStatusDataModelConverter:
|
||||
// `value.network.toBlockchain().id`). Runtime CryptoCurrency.ID expects the canonical
|
||||
// network rawId ("ethereum"), so the converter normalizes via Blockchain.fromId(...).toNetworkId().
|
||||
private val blockchainId = "ETH"
|
||||
private val canonicalNetworkRawId = "ethereum"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = CurrencyIdConverter(rawNetworkId = rawNetworkId, derivationPath = derivationPath)
|
||||
private val converter = NetworkCurrencyIdConverter(blockchainId = blockchainId, derivationPath = derivationPath)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
|
|
@ -48,19 +53,19 @@ class CurrencyIdConverterTest {
|
|||
ConvertModel(
|
||||
value = CurrencyId.createCoinId("ethereum"),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩ethereum"),
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ethereum→$derivationPathHashCode⟩ethereum"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createCoinId(""),
|
||||
expected = Result.failure(
|
||||
IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
IllegalStateException("Coin id is null for $blockchainId with $derivationPath"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
value = CurrencyId.createCoinId(" "),
|
||||
expected = Result.failure(
|
||||
IllegalStateException("Coin id is null for $rawNetworkId with $derivationPath"),
|
||||
IllegalStateException("Coin id is null for $blockchainId with $derivationPath"),
|
||||
),
|
||||
),
|
||||
// create token id
|
||||
|
|
@ -71,7 +76,7 @@ class CurrencyIdConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -82,7 +87,7 @@ class CurrencyIdConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -93,7 +98,7 @@ class CurrencyIdConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -104,7 +109,7 @@ class CurrencyIdConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -114,7 +119,7 @@ class CurrencyIdConverterTest {
|
|||
contractAddress = "",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩usdt"),
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ethereum→$derivationPathHashCode⟩usdt"),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
|
|
@ -123,7 +128,7 @@ class CurrencyIdConverterTest {
|
|||
contractAddress = " ",
|
||||
),
|
||||
expected = Result.success(
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ETH→$derivationPathHashCode⟩usdt"),
|
||||
CryptoCurrency.ID.fromValue(value = "coin⟨ethereum→$derivationPathHashCode⟩usdt"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -154,14 +159,14 @@ class CurrencyIdConverterTest {
|
|||
|
||||
private fun provideTestModels(): Collection<ConvertBackModel> = listOf(
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum"),
|
||||
value = CryptoCurrency.ID.fromValue("coin⟨ethereum→$derivationPathHashCode⟩ethereum"),
|
||||
expected = Result.success(
|
||||
CurrencyId.createCoinId("ethereum"),
|
||||
),
|
||||
),
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩usdt⚓0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CurrencyId.createTokenId(
|
||||
|
|
@ -172,7 +177,7 @@ class CurrencyIdConverterTest {
|
|||
),
|
||||
ConvertBackModel(
|
||||
value = CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
value = "token⟨ethereum→$derivationPathHashCode⟩0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
),
|
||||
expected = Result.success(
|
||||
CurrencyId.createTokenId(
|
||||
|
|
@ -184,6 +189,54 @@ class CurrencyIdConverterTest {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression coverage for [REDACTED_TASK_KEY]. Cache stores `blockchainId` in the legacy SDK format
|
||||
* (`Blockchain.id`, e.g. "ETH"), but runtime [CryptoCurrency.ID] is built using the canonical
|
||||
* network rawId (`Blockchain.toNetworkId()`, e.g. "ethereum"). The converter must bridge the
|
||||
* two formats so that IDs reconstructed from cache equal those built at runtime — otherwise
|
||||
* `NetworkStatus.Verified.amounts[currency.id]` returns null and the wallet shimmer never clears.
|
||||
*/
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class LegacyBlockchainIdNormalization {
|
||||
|
||||
@Test
|
||||
fun `convert with legacy ETH blockchainId produces id with canonical ethereum rawId`() {
|
||||
val cached = CurrencyId.createCoinId("ethereum")
|
||||
|
||||
val result = converter.convert(cached)
|
||||
|
||||
Truth.assertThat(result)
|
||||
.isEqualTo(CryptoCurrency.ID.fromValue("coin⟨$canonicalNetworkRawId→$derivationPathHashCode⟩ethereum"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert with legacy BTC blockchainId produces id with canonical bitcoin rawId`() {
|
||||
val btcDerivationPath = Network.DerivationPath.Card(value = "m/44'/0'/0'/0/0")
|
||||
val btcDerivationHash = btcDerivationPath.value.hashCode()
|
||||
val btcConverter = NetworkCurrencyIdConverter(
|
||||
blockchainId = "BTC",
|
||||
derivationPath = btcDerivationPath,
|
||||
)
|
||||
val cached = CurrencyId.createCoinId("bitcoin")
|
||||
|
||||
val result = btcConverter.convert(cached)
|
||||
|
||||
Truth.assertThat(result)
|
||||
.isEqualTo(CryptoCurrency.ID.fromValue("coin⟨bitcoin→$btcDerivationHash⟩bitcoin"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert and convertBack roundtrip preserves CurrencyId`() {
|
||||
val cached = CurrencyId.createCoinId("ethereum")
|
||||
|
||||
val runtimeId = converter.convert(cached)
|
||||
val roundTrip = converter.convertBack(runtimeId)
|
||||
|
||||
Truth.assertThat(roundTrip).isEqualTo(cached)
|
||||
}
|
||||
}
|
||||
|
||||
data class ConvertModel(val value: CurrencyId, val expected: Result<CryptoCurrency.ID>)
|
||||
|
||||
data class ConvertBackModel(val value: CryptoCurrency.ID, val expected: Result<CurrencyId>)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM.*
|
||||
|
|
@ -49,7 +50,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
),
|
||||
),
|
||||
amounts = mapOf(
|
||||
ID.fromValue(value = "coin⟨ETH→0⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue(value = "coin⟨ethereum→0⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID(
|
||||
prefix = Prefix.COIN_PREFIX,
|
||||
body = Body.NetworkId(rawId = "BTC"),
|
||||
|
|
@ -74,7 +75,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
),
|
||||
),
|
||||
expected = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "",
|
||||
type = DerivationPath.Type.NONE,
|
||||
|
|
@ -119,7 +120,7 @@ internal class NetworkStatusDataModelConverterTest {
|
|||
),
|
||||
),
|
||||
expected = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "",
|
||||
type = DerivationPath.Type.NONE,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ import java.math.BigDecimal
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class NetworkYieldSupplyStatusConverterTest {
|
||||
|
||||
private val rawNetworkId = "ETH"
|
||||
// Cache stores the SDK-level Blockchain.id (legacy format, e.g. "ETH").
|
||||
// The converter normalizes it to the canonical network rawId ("ethereum") via
|
||||
// Blockchain.fromId(...).toNetworkId() so that resulting CryptoCurrency.IDs match those
|
||||
// built at runtime from Network.rawId.
|
||||
private val blockchainId = "ETH"
|
||||
private val derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0")
|
||||
private val derivationPathHashCode = "-1843072795"
|
||||
private val converter = NetworkYieldSupplyStatusConverter(rawNetworkId, derivationPath)
|
||||
private val converter = NetworkYieldSupplyStatusConverter(blockchainId, derivationPath)
|
||||
|
||||
private val domainStatus = YieldSupplyStatus(
|
||||
isActive = true,
|
||||
|
|
@ -38,8 +42,8 @@ internal class NetworkYieldSupplyStatusConverterTest {
|
|||
|
||||
// Assert
|
||||
val expected = mapOf(
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
ID.fromValue("coin⟨ethereum→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ethereum→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
|
|
@ -49,9 +53,9 @@ internal class NetworkYieldSupplyStatusConverterTest {
|
|||
fun convertBack() {
|
||||
// Arrange
|
||||
val value = mapOf(
|
||||
ID.fromValue("coin⟨ETH→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
ID.fromValue("token⟨ETH→$derivationPathHashCode⟩usdc⚓0x1") to null,
|
||||
ID.fromValue("coin⟨ethereum→$derivationPathHashCode⟩ethereum") to domainStatus,
|
||||
ID.fromValue("token⟨ethereum→$derivationPathHashCode⟩usdt⚓0x1") to domainStatus,
|
||||
ID.fromValue("token⟨ethereum→$derivationPathHashCode⟩usdc⚓0x1") to null,
|
||||
)
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
|
|
@ -48,7 +49,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
// region Verified
|
||||
ConvertModel(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -95,12 +96,12 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
),
|
||||
amounts = mapOf(
|
||||
ID.fromValue("coin⟨ETH→3046160⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue("token⟨ETH→3046160⟩usdt⚓0x1") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue("coin⟨ethereum→3046160⟩ethereum") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
ID.fromValue("token⟨ethereum→3046160⟩usdt⚓0x1") to Amount.Loaded(value = BigDecimal.ZERO),
|
||||
),
|
||||
pendingTransactions = emptyMap(),
|
||||
yieldSupplyStatuses = mapOf(
|
||||
ID.fromValue("coin⟨ETH→3046160⟩ethereum") to YieldSupplyStatus(
|
||||
ID.fromValue("coin⟨ethereum→3046160⟩ethereum") to YieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
isAllowedToSpend = false,
|
||||
|
|
@ -116,7 +117,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
// region NoAccount
|
||||
ConvertModel(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -168,7 +169,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
// region Error
|
||||
ConvertModel(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -189,7 +190,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -205,7 +206,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = Verified(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -230,7 +231,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -255,7 +256,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
@ -276,7 +277,7 @@ internal class SimpleNetworkStatusConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = NoAccount(
|
||||
networkId = ID(network.rawId),
|
||||
networkId = ID(network.toBlockchain().id),
|
||||
derivationPath = DerivationPath(
|
||||
value = "card",
|
||||
type = DerivationPath.Type.CARD,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ internal class CommonNetworkStatusFetcherTest {
|
|||
val userWalletId = UserWalletId("011")
|
||||
val network = cryptoCurrencyFactory.ethereum.network
|
||||
val extraTokens = setOf(
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum) as CryptoCurrency.Token,
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum),
|
||||
)
|
||||
val updateException = IllegalStateException()
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ internal class CommonNetworkStatusFetcherTest {
|
|||
val userWalletId = UserWalletId("011")
|
||||
val network = cryptoCurrencyFactory.ethereum.network
|
||||
val extraTokens = setOf(
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum) as CryptoCurrency.Token,
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum),
|
||||
)
|
||||
val updateResult = model.updateResult
|
||||
val status = model.status
|
||||
|
|
@ -169,15 +169,15 @@ internal class CommonNetworkStatusFetcherTest {
|
|||
it.copy(
|
||||
amounts = mapOf(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND",
|
||||
value = "token⟨ethereum⟩NEVER-MIND⚓NEVER-MIND",
|
||||
) to NetworkStatus.Amount.NotFound,
|
||||
),
|
||||
pendingTransactions = mapOf(
|
||||
CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND") to emptySet(),
|
||||
CryptoCurrency.ID.fromValue(value = "token⟨ethereum⟩NEVER-MIND⚓NEVER-MIND") to emptySet(),
|
||||
),
|
||||
yieldSupplyStatuses = mapOf(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND",
|
||||
value = "token⟨ethereum⟩NEVER-MIND⚓NEVER-MIND",
|
||||
) to null,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.networks.multi
|
|||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.dynamicaddresses.DynamicAddressesInitializer
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
|
|
@ -27,17 +28,21 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
|
||||
private val dynamicAddressesInitializer: DynamicAddressesInitializer = mockk()
|
||||
|
||||
private val fetcher = DefaultMultiNetworkStatusFetcher(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
|
||||
dynamicAddressesInitializer = dynamicAddressesInitializer,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
|
||||
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher, dynamicAddressesInitializer)
|
||||
// No dynamic addresses restore by default
|
||||
coEvery { dynamicAddressesInitializer.getXpubs(any(), any()) } returns emptyMap()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -62,6 +67,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -70,6 +76,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -87,11 +94,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,6 +131,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -130,6 +140,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -147,11 +158,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +195,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -190,6 +204,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -207,11 +222,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +262,220 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any()) }
|
||||
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch passes xpub to correct network and null to others`() = runTest {
|
||||
// Arrange
|
||||
val xpub = "xpub_test_eth"
|
||||
val params = setupTwoNetworkParams()
|
||||
coEvery { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) } returns mapOf(ethereum.network to xpub)
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = xpub,
|
||||
)
|
||||
} returns Either.Right(Unit)
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns Either.Right(Unit)
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
assertEither(actual, expected)
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = xpub,
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch continues with null xpub for all networks if getXpubs throws`() = runTest {
|
||||
// Arrange
|
||||
val params = setupTwoNetworkParams()
|
||||
coEvery {
|
||||
dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks)
|
||||
} throws RuntimeException("XPUB derivation failed")
|
||||
|
||||
val fetchResult = Either.Right(Unit)
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns fetchResult
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns fetchResult
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
assertEither(actual, expected)
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
// getXpubs failure must not degrade network status to OnlyCache
|
||||
coVerify(inverse = true) { networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch passes xpubs to all networks returned by getXpubs`() = runTest {
|
||||
// Arrange
|
||||
val ethXpub = "xpub_eth"
|
||||
val adaXpub = "xpub_ada"
|
||||
val params = setupTwoNetworkParams()
|
||||
coEvery {
|
||||
dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks)
|
||||
} returns mapOf(ethereum.network to ethXpub, cardano.network to adaXpub)
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = ethXpub,
|
||||
)
|
||||
} returns Either.Right(Unit)
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = adaXpub,
|
||||
)
|
||||
} returns Either.Right(Unit)
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
assertEither(actual, expected)
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = ethXpub,
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = adaXpub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch calls getXpubs with exactly the networks from params`() = runTest {
|
||||
// Arrange
|
||||
val params = setupTwoNetworkParams()
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(userWalletId = any(), network = any(), networkCurrencies = any(), xpub = any())
|
||||
} returns Either.Right(Unit)
|
||||
|
||||
// Act
|
||||
fetcher(params)
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if network fetch fails when xpub is provided`() = runTest {
|
||||
// Arrange
|
||||
val xpub = "xpub_eth"
|
||||
val params = setupTwoNetworkParams()
|
||||
coEvery {
|
||||
dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks)
|
||||
} returns mapOf(ethereum.network to xpub)
|
||||
|
||||
val fetchFailure = Either.Left(IllegalStateException())
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = xpub,
|
||||
)
|
||||
} returns fetchFailure
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns Either.Right(Unit)
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Left(IllegalStateException("Failed to fetch network statuses"))
|
||||
assertEither(actual, expected)
|
||||
}
|
||||
|
||||
private fun setupTwoNetworkParams(): MultiNetworkStatusFetcher.Params {
|
||||
val params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(ethereum.network, cardano.network),
|
||||
)
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns mapOf(
|
||||
ethereum.network to listOf(ethereum),
|
||||
cardano.network to listOf(cardano),
|
||||
)
|
||||
return params
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue