Updated on 2026-08-14
This commit is contained in:
parent
57d3a102ab
commit
c501b56063
23 changed files with 296 additions and 256 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
walletsRepository = mockk()
|
||||
multiNetworkStatusSupplier = mockk()
|
||||
|
||||
enricher = UserTokensResponseAddressesEnricher(
|
||||
private val walletsRepository: WalletsRepository = mockk()
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk()
|
||||
private val enricher: UserTokensResponseAddressesEnricher = UserTokensResponseAddressesEnricher(
|
||||
walletsRepository = walletsRepository,
|
||||
dispatchers = dispatchers,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
private val userWalletId = UserWalletId("1234567890abcdef")
|
||||
|
||||
@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))
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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 {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,8 +220,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
"Unable to get a wallet manager for blockchain: ${currency.network}"
|
||||
}
|
||||
|
||||
return walletManager
|
||||
.getTransactionHistoryState(
|
||||
val transactionHistoryState = walletManager.getTransactionHistoryState(
|
||||
address = walletManager.wallet.address,
|
||||
filterType = when (currency) {
|
||||
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
|
||||
|
|
@ -232,7 +236,8 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
}
|
||||
},
|
||||
)
|
||||
.let(txHistoryStateConverter::convert)
|
||||
|
||||
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>) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 && currency.network.derivationPath.value == derivationPath.value && 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 && !it.isCustom && 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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
<ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred<AssetRequirementsCondition?>?</ID>
|
||||
<ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred<ScenarioUnavailabilityReason>?</ID>
|
||||
<ID>ExplicitCollectionElementAccessMethod:GetWalletTotalBalanceUseCase.kt$GetWalletTotalBalanceUseCase$walletBalanceCache.put(userWalletId, content)</ID>
|
||||
<ID>MultilineLambdaItParameter:AddCryptoCurrenciesUseCase.kt$AddCryptoCurrenciesUseCase${ it.network.backendId == networkId && !it.isCustom && it.contractAddress.equals(contractAddress, true) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AddCryptoCurrenciesUseCase.kt$AddCryptoCurrenciesUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -> 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 -> raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -> Unit.right() } return@either }</ID>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 -> 0 ApiEnvironment.DEV_2 -> 1 ApiEnvironment.DEV_3 -> 2 ApiEnvironment.STAGE -> 3 ApiEnvironment.MOCK -> 4 ApiEnvironment.PROD -> 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>
|
||||
|
|
|
|||
|
|
@ -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 -> 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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue