Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-03 16:00:38 +04:00
parent 57d3a102ab
commit c501b56063
23 changed files with 296 additions and 256 deletions

View file

@ -12,6 +12,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.features.hotwallet.HotWalletFeatureToggles
@ -65,6 +66,7 @@ object MarketsDomainModule {
fun provideSaveMarketTokensUseCase( fun provideSaveMarketTokensUseCase(
derivationsRepository: DerivationsRepository, derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository, marketsTokenRepository: MarketsTokenRepository,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
@ -75,6 +77,7 @@ object MarketsDomainModule {
return SaveMarketTokensUseCase( return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository, derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository, marketsTokenRepository = marketsTokenRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,

View file

@ -20,7 +20,10 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.* import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
@ -40,6 +43,7 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideAddCryptoCurrenciesUseCase( fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -48,6 +52,7 @@ internal object TokensDomainModule {
): AddCryptoCurrenciesUseCase { ): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase( return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher,

View file

@ -27,6 +27,7 @@ dependencies {
implementation(projects.domain.tokens.models) implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models) implementation(projects.domain.wallets.models)
implementation(projects.domain.networks) implementation(projects.domain.networks)
implementation(projects.domain.walletManager)
implementation(projects.domain.wallets) implementation(projects.domain.wallets)
/* Libs - SDK */ /* Libs - SDK */

View file

@ -1,55 +1,45 @@
package com.tangem.data.common.currency package com.tangem.data.common.currency
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
class UserTokensResponseAddressesEnricher @Inject constructor( class UserTokensResponseAddressesEnricher @Inject constructor(
private val walletsRepository: WalletsRepository, private val walletsRepository: WalletsRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier, private val walletManagersFacade: WalletManagersFacade,
) { ) {
suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse { suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse {
val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId) val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId)
return withContext(dispatchers.default) { return withContext(dispatchers.default) {
val networksStatuses = if (isNotificationsEnabled) { val addressByToken = if (isNotificationsEnabled) {
withTimeoutOrNull( response.tokens.associateWith { token ->
FETCH_TIMEOUT_SECONDS.seconds, val blockchain = Blockchain.fromNetworkId(token.networkId) ?: return@associateWith null
{ multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() },
).orEmpty() val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = token.derivationPath,
)
walletManager?.wallet?.addresses?.map(Address::value)
}
} else { } else {
emptySet() emptyMap()
} }
val enrichedTokens = response.tokens.map { token -> val enrichedTokens = response.tokens.map { token ->
if (isNotificationsEnabled) { if (isNotificationsEnabled) {
val matchingNetwork = networksStatuses.find { status -> val addresses = addressByToken[token] ?: return@map token
status.network.backendId == token.networkId &&
status.network.derivationPath.value == token.derivationPath
} ?: return@map token
val networkAddress = when (matchingNetwork.value) {
is NetworkStatus.Verified -> (matchingNetwork.value as NetworkStatus.Verified).address
is NetworkStatus.NoAccount -> (matchingNetwork.value as NetworkStatus.NoAccount).address
else -> null
}
val addresses = networkAddress
?.availableAddresses
?.map { it.value }
?.toList()
.orEmpty()
token.copy(addresses = addresses) token.copy(addresses = addresses)
} else { } else {
@ -60,8 +50,4 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled) response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled)
} }
} }
companion object {
private const val FETCH_TIMEOUT_SECONDS = 3
}
} }

View file

@ -13,7 +13,7 @@ import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.retryer.RetryerPool import com.tangem.utils.retryer.RetryerPool
@ -54,12 +54,12 @@ internal object DataCommonModule {
@Singleton @Singleton
fun provideUserTokensEncricher( fun provideUserTokensEncricher(
walletsRepository: WalletsRepository, walletsRepository: WalletsRepository,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier, walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): UserTokensResponseAddressesEnricher { ): UserTokensResponseAddressesEnricher {
return UserTokensResponseAddressesEnricher( return UserTokensResponseAddressesEnricher(
walletsRepository = walletsRepository, walletsRepository = walletsRepository,
multiNetworkStatusSupplier = multiNetworkStatusSupplier, walletManagersFacade = walletManagersFacade,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -1,92 +1,68 @@
package com.tangem.data.common.currency package com.tangem.data.common.currency
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.clearAllMocks import io.mockk.clearMocks
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.After import org.junit.jupiter.api.AfterEach
import org.junit.Before import org.junit.jupiter.api.Test
import org.junit.Test import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserTokensResponseAddressesEnricherTest { class UserTokensResponseAddressesEnricherTest {
private lateinit var walletsRepository: WalletsRepository private val walletsRepository: WalletsRepository = mockk()
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider() private val walletManagersFacade: WalletManagersFacade = mockk()
private lateinit var multiNetworkStatusSupplier: MultiNetworkStatusSupplier private val enricher: UserTokensResponseAddressesEnricher = UserTokensResponseAddressesEnricher(
private lateinit var enricher: UserTokensResponseAddressesEnricher walletsRepository = walletsRepository,
walletManagersFacade = walletManagersFacade,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@Before private val userWalletId = UserWalletId("1234567890abcdef")
fun setup() {
walletsRepository = mockk()
multiNetworkStatusSupplier = mockk()
enricher = UserTokensResponseAddressesEnricher( @AfterEach
walletsRepository = walletsRepository,
dispatchers = dispatchers,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
)
}
@After
fun tearDown() { fun tearDown() {
clearAllMocks() clearMocks(walletsRepository, walletManagersFacade)
}
@Test
fun `GIVEN notifications are disabled globally WHEN invoke THEN return original response`() = runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken()
val response = createUserTokensResponse(tokens = listOf(token))
// WHEN
val result = enricher(userWalletId, response)
// THEN
assertThat(result).isEqualTo(response)
} }
@Test @Test
fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() = fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() =
runTest { runTest {
// GIVEN // GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken() val token = createToken()
val response = createUserTokensResponse(tokens = listOf(token)) val response = createUserTokensResponse(tokens = listOf(token))
val walletManager = mockk<WalletManager> {
val wallet = mockk<Wallet> {
every { addresses } returns setOf(
Address(value = "0x12345", type = AddressType.Default),
)
}
every { this@mockk.wallet } returns wallet
}
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false
coEvery { coEvery {
multiNetworkStatusSupplier.invoke(any()) walletManagersFacade.getOrCreateWalletManager(
} returns flowOf( userWalletId = userWalletId,
setOf( blockchain = Blockchain.Ethereum,
NetworkStatus( derivationPath = token.derivationPath,
network = mockk { )
every { backendId } returns "ethereum" } returns walletManager
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
},
value = NetworkStatus.Verified(
address = mockk {
every { availableAddresses } returns emptySet()
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),
),
)
// WHEN // WHEN
val result = enricher(userWalletId, response) val result = enricher(userWalletId, response)
@ -100,75 +76,52 @@ class UserTokensResponseAddressesEnricherTest {
fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() = fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() =
runTest { runTest {
// GIVEN // GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken() val token = createToken()
val response = createUserTokensResponse(tokens = listOf(token)) val response = createUserTokensResponse(tokens = listOf(token))
val addresses = listOf("0x123", "0x456") val addresses = setOf(
Address(value = "0x123", type = AddressType.Default),
Address(value = "0x456", type = AddressType.Legacy),
)
val walletManager = mockk<WalletManager> {
val wallet = mockk<Wallet> {
every { this@mockk.addresses } returns addresses
}
every { this@mockk.wallet } returns wallet
}
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
coEvery { coEvery {
multiNetworkStatusSupplier.invoke(any()) walletManagersFacade.getOrCreateWalletManager(
} returns flowOf( userWalletId = userWalletId,
setOf( blockchain = Blockchain.Ethereum,
NetworkStatus( derivationPath = token.derivationPath,
network = mockk { )
every { backendId } returns "ethereum" } returns walletManager
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
},
value = NetworkStatus.Verified(
address = mockk {
every { availableAddresses } returns addresses.map { address ->
mockk<NetworkAddress.Address> {
every { value } returns address
}
}.toSet()
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),
),
)
// WHEN // WHEN
val result = enricher(userWalletId, response) val result = enricher(userWalletId, response)
// THEN // THEN
assertThat(result.tokens).hasSize(1) assertThat(result.tokens).hasSize(1)
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses) assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses.map { it.value })
} }
@Test @Test
fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest { fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest {
// GIVEN // GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken() val token = createToken()
val response = createUserTokensResponse(tokens = listOf(token)) val response = createUserTokensResponse(tokens = listOf(token))
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
coEvery { coEvery {
multiNetworkStatusSupplier.invoke(any()) walletManagersFacade.getOrCreateWalletManager(
} returns flowOf( userWalletId = userWalletId,
setOf( blockchain = Blockchain.Ethereum,
NetworkStatus( derivationPath = token.derivationPath,
network = mockk { )
every { backendId } returns "bitcoin" } returns null
every { derivationPath.value } returns "m/44'/0'/0'/0/0"
},
value = NetworkStatus.Verified(
address = mockk {
every { availableAddresses } returns emptySet()
},
amounts = emptyMap(),
pendingTransactions = emptyMap(),
yieldSupplyStatuses = emptyMap(),
source = StatusSource.ACTUAL,
),
),
),
)
// WHEN // WHEN
val result = enricher(userWalletId, response) val result = enricher(userWalletId, response)

View file

@ -2,10 +2,7 @@
<SmellBaseline> <SmellBaseline>
<ManuallySuppressedIssues/> <ManuallySuppressedIssues/>
<CurrentIssues> <CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade${ Token( name = it.name, symbol = it.symbol, contractAddress = it.contractAddress, decimals = it.decimals, id = it.id, ) }</ID>
<ID>MultilineLambdaItParameter:UpdateWalletManagerResultFactory.kt$UpdateWalletManagerResultFactory${ createCurrencyTransaction( txHistoryItemConverter = txHistoryItemConverter, data = it, ) }</ID> <ID>MultilineLambdaItParameter:UpdateWalletManagerResultFactory.kt$UpdateWalletManagerResultFactory${ createCurrencyTransaction( txHistoryItemConverter = txHistoryItemConverter, data = it, ) }</ID>
<ID>NamedArguments:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade$getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)</ID>
<ID>UnnecessaryLet:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade$let(txHistoryStateConverter::convert)</ID>
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$blockchain.getTestnetVersion()!!</ID> <ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$blockchain.getTestnetVersion()!!</ID>
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$scanResponse.secondTwinPublicKey!!</ID> <ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$scanResponse.secondTwinPublicKey!!</ID>
</CurrentIssues> </CurrentIssues>

View file

@ -85,7 +85,12 @@ internal class DefaultWalletManagersFacade @Inject constructor(
val blockchain = network.toBlockchain() val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value val derivationPath = network.derivationPath.value
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens) return getAndUpdateWalletManager(
userWallet = userWallet,
blockchain = blockchain,
derivationPath = derivationPath,
extraTokens = extraTokens,
)
} }
override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) { override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) {
@ -123,18 +128,18 @@ internal class DefaultWalletManagersFacade @Inject constructor(
if (tokenInfos.isEmpty()) return if (tokenInfos.isEmpty()) return
tokenInfos tokenInfos
.groupBy { it.network } .groupBy(TokenInfo::network)
.forEach { (network, tokenInfoList) -> .forEach { (network, tokenInfoList) ->
removeTokens( removeTokens(
userWalletId = userWalletId, userWalletId = userWalletId,
network = network, network = network,
networkTokens = tokenInfoList.map { networkTokens = tokenInfoList.map { tokenInfo ->
Token( Token(
name = it.name, name = tokenInfo.name,
symbol = it.symbol, symbol = tokenInfo.symbol,
contractAddress = it.contractAddress, contractAddress = tokenInfo.contractAddress,
decimals = it.decimals, decimals = tokenInfo.decimals,
id = it.id, id = tokenInfo.id,
) )
}, },
) )
@ -215,24 +220,24 @@ internal class DefaultWalletManagersFacade @Inject constructor(
"Unable to get a wallet manager for blockchain: ${currency.network}" "Unable to get a wallet manager for blockchain: ${currency.network}"
} }
return walletManager val transactionHistoryState = walletManager.getTransactionHistoryState(
.getTransactionHistoryState( address = walletManager.wallet.address,
address = walletManager.wallet.address, filterType = when (currency) {
filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin is CryptoCurrency.Token -> {
is CryptoCurrency.Token -> { val blockchainToken = Token(
val blockchainToken = Token( name = currency.name,
name = currency.name, symbol = currency.symbol,
symbol = currency.symbol, contractAddress = currency.contractAddress,
contractAddress = currency.contractAddress, decimals = currency.decimals,
decimals = currency.decimals, id = currency.id.rawCurrencyId?.value,
id = currency.id.rawCurrencyId?.value, )
) TransactionHistoryRequest.FilterType.Contract(blockchainToken)
TransactionHistoryRequest.FilterType.Contract(blockchainToken) }
} },
}, )
)
.let(txHistoryStateConverter::convert) return txHistoryStateConverter.convert(transactionHistoryState)
} }
override suspend fun getTxHistoryItems( override suspend fun getTxHistoryItems(
@ -366,7 +371,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
blockchain: Blockchain, blockchain: Blockchain,
derivationPath: String?, derivationPath: String?,
): WalletManager? { ): WalletManager? {
getWmInitializationMutex(blockchain, derivationPath).withLock { getWmInitializationMutex(userWalletId, blockchain, derivationPath).withLock {
val userWallet = getUserWallet(userWalletId) val userWallet = getUserWallet(userWalletId)
var walletManager = walletManagersStore.getSyncOrNull( var walletManager = walletManagersStore.getSyncOrNull(
@ -738,15 +743,24 @@ internal class DefaultWalletManagersFacade @Inject constructor(
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
} }
private fun getWmInitializationMutex(blockchain: Blockchain, derivationPath: String?): Mutex { private fun getWmInitializationMutex(
val key = createMutexMapKey(blockchain, derivationPath) userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): Mutex {
val key = createMutexMapKey(userWalletId, blockchain, derivationPath)
return wmInitializationMutexes.computeIfAbsent(key) { return wmInitializationMutexes.computeIfAbsent(key) {
Mutex() Mutex()
} }
} }
private fun createMutexMapKey(blockchain: Blockchain, derivationPath: String?): String { private fun createMutexMapKey(userWalletId: UserWalletId, blockchain: Blockchain, derivationPath: String?): String {
return blockchain.toNetworkId() + "|" + derivationPath return listOf(
userWalletId.stringValue,
blockchain.toNetworkId(),
derivationPath,
)
.joinToString(separator = "|")
} }
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) { private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {

View file

@ -11,7 +11,6 @@
<ID>MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } }</ID> <ID>MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } }</ID>
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot)</ID> <ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot)</ID>
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount)</ID> <ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount)</ID>
<ID>SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching</ID> <ID>SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching</ID>
<ID>UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks)</ID> <ID>UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks)</ID>
<ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID> <ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID>

View file

@ -1,7 +1,7 @@
========================================== ==========================================
Detekt Baseline Updater & Issue Counter Detekt Baseline Updater & Issue Counter
========================================== ==========================================
Date: 2025-12-02 13:19:49 Date: 2025-12-02 18:48:27
Step 1: Running detekt to check for new issues... Step 1: Running detekt to check for new issues...
@ -17,13 +17,13 @@ Counting issues in baseline files...
========================================== ==========================================
Summary: Summary:
Total Issues: 1435 Total Issues: 1424
Modules with Issues: 62 Modules with Issues: 62
Average Issues per Module: 23 Average Issues per Module: 22
Progress: Progress:
Fixed: 367 out of 1802 (20%) Fixed: 378 out of 1802 (20%)
Remaining: 1435 Remaining: 1424
========================================== ==========================================
All Modules with Issues (sorted by count) All Modules with Issues (sorted by count)
@ -31,8 +31,8 @@ All Modules with Issues (sorted by count)
Module Issues Module Issues
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
features/wallet/impl 148
features/markets/impl 148 features/markets/impl 148
features/wallet/impl 146
features/onboarding-v2/impl 130 features/onboarding-v2/impl 130
features/swap/impl 67 features/swap/impl 67
features/send-v2/impl 57 features/send-v2/impl 57
@ -45,9 +45,9 @@ features/manage-tokens/impl 45
features/swap-v2/impl 40 features/swap-v2/impl 40
domain/wallets 37 domain/wallets 37
features/nft/impl 34 features/nft/impl 34
features/tester/impl 31 features/tester/impl 30
domain/tokens 28
core/ui 27 core/ui 27
domain/tokens 26
common/ui 26 common/ui 26
features/swap/domain 25 features/swap/domain 25
data/visa 22 data/visa 22
@ -55,16 +55,16 @@ features/yield-supply/impl 21
features/tangempay/details/impl 21 features/tangempay/details/impl 21
data/nft 20 data/nft 20
features/swap/data 15 features/swap/data 15
data/wallets 14 data/wallets 13
data/swap 13 data/swap 13
features/token-recieve/impl 11 features/token-recieve/impl 11
features/qr-scanning/impl 11 features/qr-scanning/impl 11
domain/account/status 11
data/onramp 11 data/onramp 11
core/datasource 11 core/datasource 11
data/markets 10 data/markets 10
features/details/impl 9 features/details/impl 9
domain/staking 9 domain/staking 9
domain/account/status 9
data/yield-supply 9 data/yield-supply 9
data/networks 9 data/networks 9
features/referral/impl 8 features/referral/impl 8
@ -72,7 +72,6 @@ domain/transaction 8
libs/tangem-sdk-api 7 libs/tangem-sdk-api 7
data/txhistory 7 data/txhistory 7
features/welcome/impl 6 features/welcome/impl 6
data/wallet-manager 6
libs/visa 5 libs/visa 5
features/send-v2/api 5 features/send-v2/api 5
features/home/impl 5 features/home/impl 5
@ -87,6 +86,7 @@ features/txhistory/impl 3
features/tangempay/onboarding/impl 3 features/tangempay/onboarding/impl 3
features/create-wallet-start/impl 3 features/create-wallet-start/impl 3
domain/manage-tokens 3 domain/manage-tokens 3
data/wallet-manager 3
data/manage-tokens 3 data/manage-tokens 3
common/routing 3 common/routing 3
features/account/api 2 features/account/api 2

View file

@ -26,6 +26,7 @@ dependencies {
api(projects.domain.referral) api(projects.domain.referral)
api(projects.domain.staking) api(projects.domain.staking)
api(projects.domain.tokens) api(projects.domain.tokens)
api(projects.domain.walletManager)
api(projects.domain.wallets) api(projects.domain.wallets)
implementation(projects.libs.blockchainSdk) implementation(projects.libs.blockchainSdk)

View file

@ -7,8 +7,6 @@
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val currency = it.currency val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) currency.network.rawId == networkId.rawId.value &amp;&amp; currency.network.derivationPath.value == derivationPath.value &amp;&amp; isContractAddressMatch }</ID> <ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val currency = it.currency val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) currency.network.rawId == networkId.rawId.value &amp;&amp; currency.network.derivationPath.value == derivationPath.value &amp;&amp; isContractAddressMatch }</ID>
<ID>MultilineLambdaItParameter:ApplyTokenListSortingUseCaseV2.kt$ApplyTokenListSortingUseCaseV2${ errors[account.accountId] = it return@map account }</ID> <ID>MultilineLambdaItParameter:ApplyTokenListSortingUseCaseV2.kt$ApplyTokenListSortingUseCaseV2${ errors[account.accountId] = it return@map account }</ID>
<ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID> <ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID>
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ ExpressAsset.ID( networkId = it.network.backendId, contractAddress = (it as? CryptoCurrency.Token)?.contractAddress, ) }</ID>
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ it.network.backendId == networkId &amp;&amp; !it.isCustom &amp;&amp; it.contractAddress.equals(contractAddress, true) }</ID>
<ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID> <ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID>
<ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID> <ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID>
<ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID> <ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID>

View file

@ -16,6 +16,7 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
@ -95,6 +96,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository: AccountsCRUDRepository, accountsCRUDRepository: AccountsCRUDRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository, derivationsRepository: DerivationsRepository,
walletManagersFacade: WalletManagersFacade,
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
stakingIdFactory: StakingIdFactory, stakingIdFactory: StakingIdFactory,
networksCleaner: NetworksCleaner, networksCleaner: NetworksCleaner,
@ -107,6 +109,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository = accountsCRUDRepository, accountsCRUDRepository = accountsCRUDRepository,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository, derivationsRepository = derivationsRepository,
walletManagersFacade = walletManagersFacade,
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
stakingIdFactory = stakingIdFactory, stakingIdFactory = stakingIdFactory,
networksCleaner = networksCleaner, networksCleaner = networksCleaner,
@ -120,7 +123,6 @@ internal object AccountStatusUseCaseModule {
@Provides @Provides
@Singleton @Singleton
fun provideCryptoCurrencyBalanceFetcher( fun provideCryptoCurrencyBalanceFetcher(
accountsCRUDRepository: AccountsCRUDRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -128,7 +130,6 @@ internal object AccountStatusUseCaseModule {
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): CryptoCurrencyBalanceFetcher { ): CryptoCurrencyBalanceFetcher {
return CryptoCurrencyBalanceFetcher( return CryptoCurrencyBalanceFetcher(
accountsCRUDRepository = accountsCRUDRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,

View file

@ -21,8 +21,10 @@ import com.tangem.domain.networks.utils.NetworksCleaner
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.* import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
@ -49,6 +51,7 @@ class ManageCryptoCurrenciesUseCase(
private val accountsCRUDRepository: AccountsCRUDRepository, private val accountsCRUDRepository: AccountsCRUDRepository,
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val derivationsRepository: DerivationsRepository, private val derivationsRepository: DerivationsRepository,
private val walletManagersFacade: WalletManagersFacade,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val stakingIdFactory: StakingIdFactory, private val stakingIdFactory: StakingIdFactory,
private val networksCleaner: NetworksCleaner, private val networksCleaner: NetworksCleaner,
@ -83,6 +86,11 @@ class ManageCryptoCurrenciesUseCase(
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies() val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
.modify(add = add, remove = remove) .modify(add = add, remove = remove)
if (!modifiedCurrencyList.hasChanges) {
Timber.d("No changes in currencies, skipping")
return@withContext
}
saveAccount( saveAccount(
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()), account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
) )
@ -90,17 +98,7 @@ class ManageCryptoCurrenciesUseCase(
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
parallelUpdatingScope.launch { parallelUpdatingScope.launch {
/* syncTokens(userWalletId, modifiedCurrencyList)
* If only removal of currencies happened, we need to sync tokens. Otherwise, tokens will be synced
* when balances are refreshed for added currencies.
*/
val isOnlyRemoval = modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()
if (isOnlyRemoval) {
launch { accountsCRUDRepository.syncTokens(userWalletId) }
clearMetadata(userWalletId = userWalletId, currencies = modifiedCurrencyList.removed)
return@launch
}
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total) refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
@ -121,10 +119,10 @@ class ManageCryptoCurrenciesUseCase(
val foundToken = accountStatus.tokenList.flattenCurrencies() val foundToken = accountStatus.tokenList.flattenCurrencies()
.mapNotNull { it.currency as? CryptoCurrency.Token } .mapNotNull { it.currency as? CryptoCurrency.Token }
.firstOrNull { .firstOrNull { token ->
it.network.backendId == networkId && token.network.backendId == networkId &&
!it.isCustom && !token.isCustom &&
it.contractAddress.equals(contractAddress, true) token.contractAddress.equals(contractAddress, true)
} }
if (foundToken != null) return@withContext foundToken if (foundToken != null) return@withContext foundToken
@ -137,6 +135,8 @@ class ManageCryptoCurrenciesUseCase(
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet())) saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
parallelUpdatingScope.launch { parallelUpdatingScope.launch {
syncTokens(userWalletId, modifiedCurrencyList)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd)) cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total) refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
} }
@ -260,15 +260,40 @@ class ManageCryptoCurrenciesUseCase(
) )
} }
private suspend fun syncTokens(userWalletId: UserWalletId, modifiedCurrencyList: ModifiedCurrencyList) {
createWalletManagers(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
.onFailure { Timber.e(it, "Failed to sync tokens for wallet $userWalletId") }
}
/**
* Creates wallet managers for the given [currencies] if they do not already exist.
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
*
* @param userWalletId The ID of the user's wallet.
* @param currencies The list of cryptocurrencies for which to create wallet managers.
*/
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
for (network in networks) {
runSuspendCatching {
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
}
.onFailure { Timber.e(it, "Failed to create wallet manager for network ${network.id}") }
}
}
private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) { private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return if (currencies.isEmpty()) return
coroutineScope { coroutineScope {
launch { launch {
val assetIds = currencies.mapTo(hashSetOf()) { val assetIds = currencies.mapTo(hashSetOf()) { currency ->
ExpressAsset.ID( ExpressAsset.ID(
networkId = it.network.backendId, networkId = currency.network.backendId,
contractAddress = (it as? CryptoCurrency.Token)?.contractAddress, contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
) )
} }
@ -325,5 +350,8 @@ class ManageCryptoCurrenciesUseCase(
val added: List<CryptoCurrency>, val added: List<CryptoCurrency>,
val removed: List<CryptoCurrency>, val removed: List<CryptoCurrency>,
val total: List<CryptoCurrency>, val total: List<CryptoCurrency>,
) ) {
val hasChanges get() = added.isNotEmpty() || removed.isNotEmpty()
}
} }

View file

@ -1,8 +1,6 @@
package com.tangem.domain.account.status.utils package com.tangem.domain.account.status.utils
import arrow.core.Either import arrow.core.Either
import arrow.core.raise.either
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
@ -19,7 +17,6 @@ import timber.log.Timber
* Utility class responsible for fetching and refreshing the balances of various crypto currencies * Utility class responsible for fetching and refreshing the balances of various crypto currencies
* associated with a user's wallet. * associated with a user's wallet.
* *
* @property accountsCRUDRepository Repository for managing account data.
* @property multiNetworkStatusFetcher Fetcher for updating network statuses. * @property multiNetworkStatusFetcher Fetcher for updating network statuses.
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses. * @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
* @property multiYieldBalanceFetcher Fetcher for updating yield balances. * @property multiYieldBalanceFetcher Fetcher for updating yield balances.
@ -29,7 +26,6 @@ import timber.log.Timber
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
class CryptoCurrencyBalanceFetcher( class CryptoCurrencyBalanceFetcher(
private val accountsCRUDRepository: AccountsCRUDRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -80,22 +76,13 @@ class CryptoCurrencyBalanceFetcher(
private suspend fun refreshNetworks( private suspend fun refreshNetworks(
userWalletId: UserWalletId, userWalletId: UserWalletId,
currencies: List<CryptoCurrency>, currencies: List<CryptoCurrency>,
): Either<Throwable, Unit> = either { ): Either<Throwable, Unit> {
val either = multiNetworkStatusFetcher( return multiNetworkStatusFetcher(
params = MultiNetworkStatusFetcher.Params( params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId, userWalletId = userWalletId,
networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network), networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network),
), ),
) )
arrow.core.raise.catch(
block = { accountsCRUDRepository.syncTokens(userWalletId) },
catch = {
Timber.e(it, "Failed to sync tokens for wallet: $userWalletId")
},
)
return either
} }
private suspend fun refreshYieldBalances( private suspend fun refreshYieldBalances(

View file

@ -24,6 +24,8 @@ dependencies {
implementation(projects.domain.wallets) implementation(projects.domain.wallets)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(tangemDeps.blockchain)
/* Core */ /* Core */
api(projects.core.pagination) api(projects.core.pagination)
testImplementation(projects.core.pagination) testImplementation(projects.core.pagination)

View file

@ -61,6 +61,8 @@ class SaveManagedTokensUseCase(
parallelUpdatingScope.launch { parallelUpdatingScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
syncTokens(userWalletId = userWalletId, addedCurrencies = savedCurrencies)
launch { launch {
refreshUpdatedNetworks( refreshUpdatedNetworks(
userWalletId = userWalletId, userWalletId = userWalletId,
@ -96,6 +98,26 @@ class SaveManagedTokensUseCase(
) )
} }
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
currenciesRepository.syncTokens(userWalletId)
}
/**
* Creates wallet managers for the given [currencies] if they do not already exist.
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
*
* @param userWalletId The ID of the user's wallet.
* @param currencies The list of cryptocurrencies for which to create wallet managers.
*/
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
for (network in networks) {
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
}
}
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) { private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
multiNetworkStatusFetcher( multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params( MultiNetworkStatusFetcher.Params(
@ -103,8 +125,6 @@ class SaveManagedTokensUseCase(
networks = addedCurrencies.map(CryptoCurrency::network).toSet(), networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
), ),
) )
currenciesRepository.syncTokens(userWalletId)
} }
private suspend fun refreshUpdatedYieldBalances( private suspend fun refreshUpdatedYieldBalances(

View file

@ -21,6 +21,7 @@ dependencies {
api(projects.domain.networks) api(projects.domain.networks)
api(projects.domain.staking) api(projects.domain.staking)
api(projects.domain.quotes) api(projects.domain.quotes)
api(projects.domain.walletManager)
api(projects.domain.wallets) api(projects.domain.wallets)
api(projects.domain.wallets.models) api(projects.domain.wallets.models)
api(projects.domain.promo) api(projects.domain.promo)

View file

@ -11,6 +11,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
@ -31,6 +32,7 @@ import kotlinx.coroutines.withContext
class SaveMarketTokensUseCase( class SaveMarketTokensUseCase(
private val derivationsRepository: DerivationsRepository, private val derivationsRepository: DerivationsRepository,
private val marketsTokenRepository: MarketsTokenRepository, private val marketsTokenRepository: MarketsTokenRepository,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
@ -80,6 +82,8 @@ class SaveMarketTokensUseCase(
parallelUpdatingScope.launch { parallelUpdatingScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
syncTokens(userWalletId, savedCurrencies)
launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) } launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) }
launch { refreshUpdatedYieldBalances(userWalletId, savedCurrencies) } launch { refreshUpdatedYieldBalances(userWalletId, savedCurrencies) }
launch { refreshUpdatedQuotes(savedCurrencies) } launch { refreshUpdatedQuotes(savedCurrencies) }
@ -88,6 +92,26 @@ class SaveMarketTokensUseCase(
} }
} }
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
currenciesRepository.syncTokens(userWalletId)
}
/**
* Creates wallet managers for the given [currencies] if they do not already exist.
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
*
* @param userWalletId The ID of the user's wallet.
* @param currencies The list of cryptocurrencies for which to create wallet managers.
*/
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
for (network in networks) {
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
}
}
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) { private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
multiNetworkStatusFetcher( multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params( MultiNetworkStatusFetcher.Params(
@ -95,7 +119,6 @@ class SaveMarketTokensUseCase(
networks = addedCurrencies.map(CryptoCurrency::network).toSet(), networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
), ),
) )
currenciesRepository.syncTokens(userWalletId)
} }
private suspend fun refreshUpdatedYieldBalances( private suspend fun refreshUpdatedYieldBalances(

View file

@ -6,8 +6,6 @@
<ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred&lt;AssetRequirementsCondition?&gt;?</ID> <ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred&lt;AssetRequirementsCondition?&gt;?</ID>
<ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred&lt;ScenarioUnavailabilityReason&gt;?</ID> <ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred&lt;ScenarioUnavailabilityReason&gt;?</ID>
<ID>ExplicitCollectionElementAccessMethod:GetWalletTotalBalanceUseCase.kt$GetWalletTotalBalanceUseCase$walletBalanceCache.put(userWalletId, content)</ID> <ID>ExplicitCollectionElementAccessMethod:GetWalletTotalBalanceUseCase.kt$GetWalletTotalBalanceUseCase$walletBalanceCache.put(userWalletId, content)</ID>
<ID>MultilineLambdaItParameter:AddCryptoCurrenciesUseCase.kt$AddCryptoCurrenciesUseCase${ it.network.backendId == networkId &amp;&amp; !it.isCustom &amp;&amp; it.contractAddress.equals(contractAddress, true) }</ID>
<ID>MultilineLambdaItParameter:AddCryptoCurrenciesUseCase.kt$AddCryptoCurrenciesUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -&gt; raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -&gt; Unit.right() } return@either }</ID>
<ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(rawCurrencyId = it)) .firstOrNull() }</ID> <ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(rawCurrencyId = it)) .firstOrNull() }</ID>
<ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ val exception = IllegalStateException("$it") Error.DataError(exception) }</ID> <ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ val exception = IllegalStateException("$it") Error.DataError(exception) }</ID>
<ID>MultilineLambdaItParameter:FetchCurrencyStatusUseCase.kt$FetchCurrencyStatusUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -&gt; raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -&gt; Unit.right() } return@either }</ID> <ID>MultilineLambdaItParameter:FetchCurrencyStatusUseCase.kt$FetchCurrencyStatusUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -&gt; raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -&gt; Unit.right() } return@either }</ID>

View file

@ -14,6 +14,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
@ -28,6 +29,7 @@ import kotlinx.coroutines.coroutineScope
@Suppress("LongParameterList") @Suppress("LongParameterList")
class AddCryptoCurrenciesUseCase( class AddCryptoCurrenciesUseCase(
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val walletManagersFacade: WalletManagersFacade,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher, private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -73,9 +75,11 @@ class AddCryptoCurrenciesUseCase(
) )
val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either
addCurrencies(userWalletId, currencyToAdd) val addedCurrencies = addCurrencies(userWalletId, currencyToAdd)
coroutineScope { coroutineScope {
syncTokens(userWalletId, addedCurrencies)
awaitAll( awaitAll(
async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) }, async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) },
async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) }, async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) },
@ -102,18 +106,20 @@ class AddCryptoCurrenciesUseCase(
val foundToken = existingCurrencies val foundToken = existingCurrencies
.filterIsInstance<CryptoCurrency.Token>() .filterIsInstance<CryptoCurrency.Token>()
.firstOrNull { .firstOrNull { token ->
it.network.backendId == networkId && token.network.backendId == networkId &&
!it.isCustom && !token.isCustom &&
it.contractAddress.equals(contractAddress, true) token.contractAddress.equals(contractAddress, true)
} }
if (foundToken != null) { if (foundToken != null) {
return@either foundToken return@either foundToken
} }
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId) val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
addCurrencies(userWalletId, tokenToAdd) val addedCurrencies = addCurrencies(userWalletId, tokenToAdd)
coroutineScope { coroutineScope {
syncTokens(userWalletId = userWalletId, addedCurrencies = addedCurrencies)
awaitAll( awaitAll(
async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) }, async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) },
async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) }, async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) },
@ -124,6 +130,26 @@ class AddCryptoCurrenciesUseCase(
tokenToAdd tokenToAdd
} }
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
currenciesRepository.syncTokens(userWalletId)
}
/**
* Creates wallet managers for the given [currencies] if they do not already exist.
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
*
* @param userWalletId The ID of the user's wallet.
* @param currencies The list of cryptocurrencies for which to create wallet managers.
*/
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
for (network in networks) {
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
}
}
/** /**
* Refreshes the network statuses for tokens that have corresponding coins in the * Refreshes the network statuses for tokens that have corresponding coins in the
* [existingCurrencies] list. * [existingCurrencies] list.
@ -149,8 +175,6 @@ class AddCryptoCurrenciesUseCase(
networks = setOfNotNull(networksToUpdate, networkToUpdate), networks = setOfNotNull(networksToUpdate, networkToUpdate),
), ),
) )
currenciesRepository.syncTokens(userWalletId)
} }
private suspend fun refreshUpdatedYieldBalances( private suspend fun refreshUpdatedYieldBalances(
@ -162,9 +186,9 @@ class AddCryptoCurrenciesUseCase(
currencyId = addedCurrency.id, currencyId = addedCurrency.id,
network = addedCurrency.network, network = addedCurrency.network,
) )
.getOrElse { .getOrElse { error ->
when (it) { when (error) {
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it")) is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$error"))
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right() StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
} }
@ -205,12 +229,14 @@ class AddCryptoCurrenciesUseCase(
) )
} }
private suspend fun Raise<Throwable>.addCurrencies(userWalletId: UserWalletId, currency: CryptoCurrency) { private suspend fun Raise<Throwable>.addCurrencies(
catch( userWalletId: UserWalletId,
{ currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) }, currency: CryptoCurrency,
) { ): List<CryptoCurrency> {
raise(it) return catch(
} block = { currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) },
catch = ::raise,
)
} }
/** /**

View file

@ -9,7 +9,6 @@
<ID>ExplicitCollectionElementAccessMethod:TestPushAddKeyDataTransformer.kt$TestPushAddKeyDataTransformer$mutableData.set(index = index, updated)</ID> <ID>ExplicitCollectionElementAccessMethod:TestPushAddKeyDataTransformer.kt$TestPushAddKeyDataTransformer$mutableData.set(index = index, updated)</ID>
<ID>ExplicitCollectionElementAccessMethod:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</ID> <ID>ExplicitCollectionElementAccessMethod:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</ID>
<ID>MaxChainedCallsOnSameLine:BlockchainProvidersScreen.kt$ProvidersDnDTarget$event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()</ID> <ID>MaxChainedCallsOnSameLine:BlockchainProvidersScreen.kt$ProvidersDnDTarget$event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()</ID>
<ID>MultilineLambdaItParameter:ApiEnvironmentComparator.kt$ApiEnvironmentComparator${ when (it) { ApiEnvironment.DEV -&gt; 0 ApiEnvironment.DEV_2 -&gt; 1 ApiEnvironment.DEV_3 -&gt; 2 ApiEnvironment.STAGE -&gt; 3 ApiEnvironment.MOCK -&gt; 4 ApiEnvironment.PROD -&gt; 5 } }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersScreen.kt${ value = it state.onValueChange(it.text) }</ID> <ID>MultilineLambdaItParameter:BlockchainProvidersScreen.kt${ value = it state.onValueChange(it.text) }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ if (it.blockchainId == blockchainId) { it.update() } else { it } }</ID> <ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ if (it.blockchainId == blockchainId) { it.update() } else { it } }</ID>
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ it.copyProvidersUM(blockchainId = id) { copy( addPublicProviderDialog = addPublicProviderDialog.copy( hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), ), ) } }</ID> <ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ it.copyProvidersUM(blockchainId = id) { copy( addPublicProviderDialog = addPublicProviderDialog.copy( hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), ), ) } }</ID>

View file

@ -85,7 +85,6 @@
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ Timber.e( """ Unable to get user wallet |- ID: $userWalletId |- Exception: $it """.trimIndent(), ) null }</ID> <ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ Timber.e( """ Unable to get user wallet |- ID: $userWalletId |- Exception: $it """.trimIndent(), ) null }</ID>
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ router.openOnboardingScreen( scanResponse = it.scanResponse, continueBackup = true, ) }</ID> <ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ router.openOnboardingScreen( scanResponse = it.scanResponse, continueBackup = true, ) }</ID>
<ID>MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() }</ID> <ID>MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() }</ID>
<ID>MultilineLambdaItParameter:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) { it.walletCardState.id } else { null } }</ID>
<ID>NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)</ID> <ID>NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)</ID>
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)</ID> <ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)</ID>
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)</ID> <ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)</ID>
@ -109,7 +108,6 @@
<ID>NoNameShadowing:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ it is TokensListItemUM.Token }</ID> <ID>NoNameShadowing:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ it is TokensListItemUM.Token }</ID>
<ID>NoNameShadowing:WalletNFTItem.kt$modifier</ID> <ID>NoNameShadowing:WalletNFTItem.kt$modifier</ID>
<ID>NoNameShadowing:WalletScreen.kt${ it.organizeTokensButtonConfig?.let { config -&gt; organizeTokensButton( modifier = itemModifier, isEnabled = config.isEnabled, onClick = config.onClick, ) } }</ID> <ID>NoNameShadowing:WalletScreen.kt${ it.organizeTokensButtonConfig?.let { config -&gt; organizeTokensButton( modifier = itemModifier, isEnabled = config.isEnabled, onClick = config.onClick, ) } }</ID>
<ID>NoNameShadowing:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ it is WalletNotification.FinishWalletActivation }</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinCurrency</ID> <ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinCurrency</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID> <ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID> <ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID>