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.multi.MultiYieldBalanceFetcher
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.legacy.UserWalletsListManager
import com.tangem.features.hotwallet.HotWalletFeatureToggles
@ -65,6 +66,7 @@ object MarketsDomainModule {
fun provideSaveMarketTokensUseCase(
derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
@ -75,6 +77,7 @@ object MarketsDomainModule {
return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
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.operations.BaseCurrencyStatusOperations
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.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
@ -40,6 +43,7 @@ internal object TokensDomainModule {
@Singleton
fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -48,6 +52,7 @@ internal object TokensDomainModule {
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,

View file

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

View file

@ -1,55 +1,45 @@
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.domain.models.network.NetworkStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
class UserTokensResponseAddressesEnricher @Inject constructor(
private val walletsRepository: WalletsRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
private val walletManagersFacade: WalletManagersFacade,
) {
suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse {
val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId)
return withContext(dispatchers.default) {
val networksStatuses = if (isNotificationsEnabled) {
withTimeoutOrNull(
FETCH_TIMEOUT_SECONDS.seconds,
{ multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() },
).orEmpty()
val addressByToken = if (isNotificationsEnabled) {
response.tokens.associateWith { token ->
val blockchain = Blockchain.fromNetworkId(token.networkId) ?: return@associateWith null
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = token.derivationPath,
)
walletManager?.wallet?.addresses?.map(Address::value)
}
} else {
emptySet()
emptyMap()
}
val enrichedTokens = response.tokens.map { token ->
if (isNotificationsEnabled) {
val matchingNetwork = networksStatuses.find { status ->
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()
val addresses = addressByToken[token] ?: return@map token
token.copy(addresses = addresses)
} else {
@ -60,8 +50,4 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
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.domain.account.featuretoggle.AccountsFeatureToggles
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.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.retryer.RetryerPool
@ -54,12 +54,12 @@ internal object DataCommonModule {
@Singleton
fun provideUserTokensEncricher(
walletsRepository: WalletsRepository,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
): UserTokensResponseAddressesEnricher {
return UserTokensResponseAddressesEnricher(
walletsRepository = walletsRepository,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
walletManagersFacade = walletManagersFacade,
dispatchers = dispatchers,
)
}

View file

@ -1,92 +1,68 @@
package com.tangem.data.common.currency
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.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.networks.multi.MultiNetworkStatusSupplier
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.clearAllMocks
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserTokensResponseAddressesEnricherTest {
private lateinit var walletsRepository: WalletsRepository
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
private lateinit var multiNetworkStatusSupplier: MultiNetworkStatusSupplier
private lateinit var enricher: UserTokensResponseAddressesEnricher
private val walletsRepository: WalletsRepository = mockk()
private val walletManagersFacade: WalletManagersFacade = mockk()
private val enricher: UserTokensResponseAddressesEnricher = UserTokensResponseAddressesEnricher(
walletsRepository = walletsRepository,
walletManagersFacade = walletManagersFacade,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@Before
fun setup() {
walletsRepository = mockk()
multiNetworkStatusSupplier = mockk()
private val userWalletId = UserWalletId("1234567890abcdef")
enricher = UserTokensResponseAddressesEnricher(
walletsRepository = walletsRepository,
dispatchers = dispatchers,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
)
}
@After
@AfterEach
fun tearDown() {
clearAllMocks()
}
@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)
clearMocks(walletsRepository, walletManagersFacade)
}
@Test
fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() =
runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken()
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 {
multiNetworkStatusSupplier.invoke(any())
} returns flowOf(
setOf(
NetworkStatus(
network = mockk {
every { backendId } returns "ethereum"
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,
),
),
),
)
walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = Blockchain.Ethereum,
derivationPath = token.derivationPath,
)
} returns walletManager
// WHEN
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`() =
runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken()
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 {
multiNetworkStatusSupplier.invoke(any())
} returns flowOf(
setOf(
NetworkStatus(
network = mockk {
every { backendId } returns "ethereum"
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,
),
),
),
)
walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = Blockchain.Ethereum,
derivationPath = token.derivationPath,
)
} returns walletManager
// WHEN
val result = enricher(userWalletId, response)
// THEN
assertThat(result.tokens).hasSize(1)
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses)
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses.map { it.value })
}
@Test
fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val token = createToken()
val response = createUserTokensResponse(tokens = listOf(token))
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
coEvery {
multiNetworkStatusSupplier.invoke(any())
} returns flowOf(
setOf(
NetworkStatus(
network = mockk {
every { backendId } returns "bitcoin"
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,
),
),
),
)
walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = Blockchain.Ethereum,
derivationPath = token.derivationPath,
)
} returns null
// WHEN
val result = enricher(userWalletId, response)

View file

@ -2,10 +2,7 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<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>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$scanResponse.secondTwinPublicKey!!</ID>
</CurrentIssues>

View file

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

View file

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

View file

@ -26,6 +26,7 @@ dependencies {
api(projects.domain.referral)
api(projects.domain.staking)
api(projects.domain.tokens)
api(projects.domain.walletManager)
api(projects.domain.wallets)
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: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: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:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</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.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -95,6 +96,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository: AccountsCRUDRepository,
currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository,
walletManagersFacade: WalletManagersFacade,
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
stakingIdFactory: StakingIdFactory,
networksCleaner: NetworksCleaner,
@ -107,6 +109,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository = accountsCRUDRepository,
currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository,
walletManagersFacade = walletManagersFacade,
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
stakingIdFactory = stakingIdFactory,
networksCleaner = networksCleaner,
@ -120,7 +123,6 @@ internal object AccountStatusUseCaseModule {
@Provides
@Singleton
fun provideCryptoCurrencyBalanceFetcher(
accountsCRUDRepository: AccountsCRUDRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -128,7 +130,6 @@ internal object AccountStatusUseCaseModule {
dispatchers: CoroutineDispatcherProvider,
): CryptoCurrencyBalanceFetcher {
return CryptoCurrencyBalanceFetcher(
accountsCRUDRepository = accountsCRUDRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
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.utils.StakingCleaner
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.*
import timber.log.Timber
@ -49,6 +51,7 @@ class ManageCryptoCurrenciesUseCase(
private val accountsCRUDRepository: AccountsCRUDRepository,
private val currenciesRepository: CurrenciesRepository,
private val derivationsRepository: DerivationsRepository,
private val walletManagersFacade: WalletManagersFacade,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val stakingIdFactory: StakingIdFactory,
private val networksCleaner: NetworksCleaner,
@ -83,6 +86,11 @@ class ManageCryptoCurrenciesUseCase(
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
.modify(add = add, remove = remove)
if (!modifiedCurrencyList.hasChanges) {
Timber.d("No changes in currencies, skipping")
return@withContext
}
saveAccount(
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
)
@ -90,17 +98,7 @@ class ManageCryptoCurrenciesUseCase(
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
parallelUpdatingScope.launch {
/*
* 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
}
syncTokens(userWalletId, modifiedCurrencyList)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
@ -121,10 +119,10 @@ class ManageCryptoCurrenciesUseCase(
val foundToken = accountStatus.tokenList.flattenCurrencies()
.mapNotNull { it.currency as? CryptoCurrency.Token }
.firstOrNull {
it.network.backendId == networkId &&
!it.isCustom &&
it.contractAddress.equals(contractAddress, true)
.firstOrNull { token ->
token.network.backendId == networkId &&
!token.isCustom &&
token.contractAddress.equals(contractAddress, true)
}
if (foundToken != null) return@withContext foundToken
@ -137,6 +135,8 @@ class ManageCryptoCurrenciesUseCase(
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
parallelUpdatingScope.launch {
syncTokens(userWalletId, modifiedCurrencyList)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
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>) {
if (currencies.isEmpty()) return
coroutineScope {
launch {
val assetIds = currencies.mapTo(hashSetOf()) {
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
ExpressAsset.ID(
networkId = it.network.backendId,
contractAddress = (it as? CryptoCurrency.Token)?.contractAddress,
networkId = currency.network.backendId,
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
)
}
@ -325,5 +350,8 @@ class ManageCryptoCurrenciesUseCase(
val added: List<CryptoCurrency>,
val removed: 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
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.wallet.UserWalletId
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
* associated with a user's wallet.
*
* @property accountsCRUDRepository Repository for managing account data.
* @property multiNetworkStatusFetcher Fetcher for updating network statuses.
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
* @property multiYieldBalanceFetcher Fetcher for updating yield balances.
@ -29,7 +26,6 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
class CryptoCurrencyBalanceFetcher(
private val accountsCRUDRepository: AccountsCRUDRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -80,22 +76,13 @@ class CryptoCurrencyBalanceFetcher(
private suspend fun refreshNetworks(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
): Either<Throwable, Unit> = either {
val either = multiNetworkStatusFetcher(
): Either<Throwable, Unit> {
return multiNetworkStatusFetcher(
params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
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(

View file

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

View file

@ -61,6 +61,8 @@ class SaveManagedTokensUseCase(
parallelUpdatingScope.launch {
withContext(NonCancellable) {
syncTokens(userWalletId = userWalletId, addedCurrencies = savedCurrencies)
launch {
refreshUpdatedNetworks(
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>) {
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
@ -103,8 +125,6 @@ class SaveManagedTokensUseCase(
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
),
)
currenciesRepository.syncTokens(userWalletId)
}
private suspend fun refreshUpdatedYieldBalances(

View file

@ -21,6 +21,7 @@ dependencies {
api(projects.domain.networks)
api(projects.domain.staking)
api(projects.domain.quotes)
api(projects.domain.walletManager)
api(projects.domain.wallets)
api(projects.domain.wallets.models)
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.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
@ -31,6 +32,7 @@ import kotlinx.coroutines.withContext
class SaveMarketTokensUseCase(
private val derivationsRepository: DerivationsRepository,
private val marketsTokenRepository: MarketsTokenRepository,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
@ -80,6 +82,8 @@ class SaveMarketTokensUseCase(
parallelUpdatingScope.launch {
withContext(NonCancellable) {
syncTokens(userWalletId, savedCurrencies)
launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) }
launch { refreshUpdatedYieldBalances(userWalletId, 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>) {
multiNetworkStatusFetcher(
MultiNetworkStatusFetcher.Params(
@ -95,7 +119,6 @@ class SaveMarketTokensUseCase(
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
),
)
currenciesRepository.syncTokens(userWalletId)
}
private suspend fun refreshUpdatedYieldBalances(

View file

@ -6,8 +6,6 @@
<ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred&lt;AssetRequirementsCondition?&gt;?</ID>
<ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred&lt;ScenarioUnavailabilityReason&gt;?</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${ 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>

View file

@ -14,6 +14,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@ -28,6 +29,7 @@ import kotlinx.coroutines.coroutineScope
@Suppress("LongParameterList")
class AddCryptoCurrenciesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val walletManagersFacade: WalletManagersFacade,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -73,9 +75,11 @@ class AddCryptoCurrenciesUseCase(
)
val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either
addCurrencies(userWalletId, currencyToAdd)
val addedCurrencies = addCurrencies(userWalletId, currencyToAdd)
coroutineScope {
syncTokens(userWalletId, addedCurrencies)
awaitAll(
async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) },
async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) },
@ -102,18 +106,20 @@ class AddCryptoCurrenciesUseCase(
val foundToken = existingCurrencies
.filterIsInstance<CryptoCurrency.Token>()
.firstOrNull {
it.network.backendId == networkId &&
!it.isCustom &&
it.contractAddress.equals(contractAddress, true)
.firstOrNull { token ->
token.network.backendId == networkId &&
!token.isCustom &&
token.contractAddress.equals(contractAddress, true)
}
if (foundToken != null) {
return@either foundToken
}
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
addCurrencies(userWalletId, tokenToAdd)
val addedCurrencies = addCurrencies(userWalletId, tokenToAdd)
coroutineScope {
syncTokens(userWalletId = userWalletId, addedCurrencies = addedCurrencies)
awaitAll(
async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) },
async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) },
@ -124,6 +130,26 @@ class AddCryptoCurrenciesUseCase(
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
* [existingCurrencies] list.
@ -149,8 +175,6 @@ class AddCryptoCurrenciesUseCase(
networks = setOfNotNull(networksToUpdate, networkToUpdate),
),
)
currenciesRepository.syncTokens(userWalletId)
}
private suspend fun refreshUpdatedYieldBalances(
@ -162,9 +186,9 @@ class AddCryptoCurrenciesUseCase(
currencyId = addedCurrency.id,
network = addedCurrency.network,
)
.getOrElse {
when (it) {
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it"))
.getOrElse { error ->
when (error) {
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$error"))
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
}
@ -205,12 +229,14 @@ class AddCryptoCurrenciesUseCase(
)
}
private suspend fun Raise<Throwable>.addCurrencies(userWalletId: UserWalletId, currency: CryptoCurrency) {
catch(
{ currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) },
) {
raise(it)
}
private suspend fun Raise<Throwable>.addCurrencies(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): List<CryptoCurrency> {
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:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</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: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>

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${ 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: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:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, 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: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:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ it is WalletNotification.FinishWalletActivation }</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinCurrency</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID>
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID>