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.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
|
|
@ -65,6 +66,7 @@ object MarketsDomainModule {
|
||||||
fun provideSaveMarketTokensUseCase(
|
fun provideSaveMarketTokensUseCase(
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
marketsTokenRepository: MarketsTokenRepository,
|
marketsTokenRepository: MarketsTokenRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
|
|
@ -75,6 +77,7 @@ object MarketsDomainModule {
|
||||||
return SaveMarketTokensUseCase(
|
return SaveMarketTokensUseCase(
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
marketsTokenRepository = marketsTokenRepository,
|
marketsTokenRepository = marketsTokenRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||||
import com.tangem.domain.tokens.*
|
import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
|
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||||
|
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
||||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||||
|
|
@ -40,6 +43,7 @@ internal object TokensDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAddCryptoCurrenciesUseCase(
|
fun provideAddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||||
|
|
@ -48,6 +52,7 @@ internal object TokensDomainModule {
|
||||||
): AddCryptoCurrenciesUseCase {
|
): AddCryptoCurrenciesUseCase {
|
||||||
return AddCryptoCurrenciesUseCase(
|
return AddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ dependencies {
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.networks)
|
implementation(projects.domain.networks)
|
||||||
|
implementation(projects.domain.walletManager)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
|
|
||||||
/* Libs - SDK */
|
/* Libs - SDK */
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,45 @@
|
||||||
package com.tangem.data.common.currency
|
package com.tangem.data.common.currency
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.address.Address
|
||||||
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
import com.tangem.domain.models.network.NetworkStatus
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
class UserTokensResponseAddressesEnricher @Inject constructor(
|
class UserTokensResponseAddressesEnricher @Inject constructor(
|
||||||
private val walletsRepository: WalletsRepository,
|
private val walletsRepository: WalletsRepository,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse {
|
suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse {
|
||||||
val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId)
|
val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId)
|
||||||
|
|
||||||
return withContext(dispatchers.default) {
|
return withContext(dispatchers.default) {
|
||||||
val networksStatuses = if (isNotificationsEnabled) {
|
val addressByToken = if (isNotificationsEnabled) {
|
||||||
withTimeoutOrNull(
|
response.tokens.associateWith { token ->
|
||||||
FETCH_TIMEOUT_SECONDS.seconds,
|
val blockchain = Blockchain.fromNetworkId(token.networkId) ?: return@associateWith null
|
||||||
{ multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() },
|
|
||||||
).orEmpty()
|
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
blockchain = blockchain,
|
||||||
|
derivationPath = token.derivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
walletManager?.wallet?.addresses?.map(Address::value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
emptySet()
|
emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
val enrichedTokens = response.tokens.map { token ->
|
val enrichedTokens = response.tokens.map { token ->
|
||||||
if (isNotificationsEnabled) {
|
if (isNotificationsEnabled) {
|
||||||
val matchingNetwork = networksStatuses.find { status ->
|
val addresses = addressByToken[token] ?: return@map token
|
||||||
status.network.backendId == token.networkId &&
|
|
||||||
status.network.derivationPath.value == token.derivationPath
|
|
||||||
} ?: return@map token
|
|
||||||
|
|
||||||
val networkAddress = when (matchingNetwork.value) {
|
|
||||||
is NetworkStatus.Verified -> (matchingNetwork.value as NetworkStatus.Verified).address
|
|
||||||
is NetworkStatus.NoAccount -> (matchingNetwork.value as NetworkStatus.NoAccount).address
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val addresses = networkAddress
|
|
||||||
?.availableAddresses
|
|
||||||
?.map { it.value }
|
|
||||||
?.toList()
|
|
||||||
.orEmpty()
|
|
||||||
|
|
||||||
token.copy(addresses = addresses)
|
token.copy(addresses = addresses)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -60,8 +50,4 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
|
||||||
response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled)
|
response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val FETCH_TIMEOUT_SECONDS = 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||||
import com.tangem.domain.demo.models.DemoConfig
|
import com.tangem.domain.demo.models.DemoConfig
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.retryer.RetryerPool
|
import com.tangem.utils.retryer.RetryerPool
|
||||||
|
|
@ -54,12 +54,12 @@ internal object DataCommonModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideUserTokensEncricher(
|
fun provideUserTokensEncricher(
|
||||||
walletsRepository: WalletsRepository,
|
walletsRepository: WalletsRepository,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): UserTokensResponseAddressesEnricher {
|
): UserTokensResponseAddressesEnricher {
|
||||||
return UserTokensResponseAddressesEnricher(
|
return UserTokensResponseAddressesEnricher(
|
||||||
walletsRepository = walletsRepository,
|
walletsRepository = walletsRepository,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
walletManagersFacade = walletManagersFacade,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,68 @@
|
||||||
package com.tangem.data.common.currency
|
package com.tangem.data.common.currency
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.Wallet
|
||||||
|
import com.tangem.blockchain.common.WalletManager
|
||||||
|
import com.tangem.blockchain.common.address.Address
|
||||||
|
import com.tangem.blockchain.common.address.AddressType
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.models.network.NetworkAddress
|
|
||||||
import com.tangem.domain.models.network.NetworkStatus
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearMocks
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.Before
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class UserTokensResponseAddressesEnricherTest {
|
class UserTokensResponseAddressesEnricherTest {
|
||||||
|
|
||||||
private lateinit var walletsRepository: WalletsRepository
|
private val walletsRepository: WalletsRepository = mockk()
|
||||||
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
|
private val walletManagersFacade: WalletManagersFacade = mockk()
|
||||||
private lateinit var multiNetworkStatusSupplier: MultiNetworkStatusSupplier
|
private val enricher: UserTokensResponseAddressesEnricher = UserTokensResponseAddressesEnricher(
|
||||||
private lateinit var enricher: UserTokensResponseAddressesEnricher
|
walletsRepository = walletsRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
)
|
||||||
|
|
||||||
@Before
|
private val userWalletId = UserWalletId("1234567890abcdef")
|
||||||
fun setup() {
|
|
||||||
walletsRepository = mockk()
|
|
||||||
multiNetworkStatusSupplier = mockk()
|
|
||||||
|
|
||||||
enricher = UserTokensResponseAddressesEnricher(
|
@AfterEach
|
||||||
walletsRepository = walletsRepository,
|
|
||||||
dispatchers = dispatchers,
|
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
clearAllMocks()
|
clearMocks(walletsRepository, walletManagersFacade)
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN notifications are disabled globally WHEN invoke THEN return original response`() = runTest {
|
|
||||||
// GIVEN
|
|
||||||
val userWalletId = UserWalletId("1234567890abcdef")
|
|
||||||
val token = createToken()
|
|
||||||
val response = createUserTokensResponse(tokens = listOf(token))
|
|
||||||
|
|
||||||
// WHEN
|
|
||||||
val result = enricher(userWalletId, response)
|
|
||||||
|
|
||||||
// THEN
|
|
||||||
assertThat(result).isEqualTo(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() =
|
fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() =
|
||||||
runTest {
|
runTest {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val userWalletId = UserWalletId("1234567890abcdef")
|
|
||||||
val token = createToken()
|
val token = createToken()
|
||||||
val response = createUserTokensResponse(tokens = listOf(token))
|
val response = createUserTokensResponse(tokens = listOf(token))
|
||||||
|
val walletManager = mockk<WalletManager> {
|
||||||
|
val wallet = mockk<Wallet> {
|
||||||
|
every { addresses } returns setOf(
|
||||||
|
Address(value = "0x12345", type = AddressType.Default),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
every { this@mockk.wallet } returns wallet
|
||||||
|
}
|
||||||
|
|
||||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false
|
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.invoke(any())
|
walletManagersFacade.getOrCreateWalletManager(
|
||||||
} returns flowOf(
|
userWalletId = userWalletId,
|
||||||
setOf(
|
blockchain = Blockchain.Ethereum,
|
||||||
NetworkStatus(
|
derivationPath = token.derivationPath,
|
||||||
network = mockk {
|
)
|
||||||
every { backendId } returns "ethereum"
|
} returns walletManager
|
||||||
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
|
|
||||||
},
|
|
||||||
value = NetworkStatus.Verified(
|
|
||||||
address = mockk {
|
|
||||||
every { availableAddresses } returns emptySet()
|
|
||||||
},
|
|
||||||
amounts = emptyMap(),
|
|
||||||
pendingTransactions = emptyMap(),
|
|
||||||
yieldSupplyStatuses = emptyMap(),
|
|
||||||
source = StatusSource.ACTUAL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = enricher(userWalletId, response)
|
val result = enricher(userWalletId, response)
|
||||||
|
|
@ -100,75 +76,52 @@ class UserTokensResponseAddressesEnricherTest {
|
||||||
fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() =
|
fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() =
|
||||||
runTest {
|
runTest {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val userWalletId = UserWalletId("1234567890abcdef")
|
|
||||||
val token = createToken()
|
val token = createToken()
|
||||||
val response = createUserTokensResponse(tokens = listOf(token))
|
val response = createUserTokensResponse(tokens = listOf(token))
|
||||||
val addresses = listOf("0x123", "0x456")
|
val addresses = setOf(
|
||||||
|
Address(value = "0x123", type = AddressType.Default),
|
||||||
|
Address(value = "0x456", type = AddressType.Legacy),
|
||||||
|
)
|
||||||
|
|
||||||
|
val walletManager = mockk<WalletManager> {
|
||||||
|
val wallet = mockk<Wallet> {
|
||||||
|
every { this@mockk.addresses } returns addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
every { this@mockk.wallet } returns wallet
|
||||||
|
}
|
||||||
|
|
||||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.invoke(any())
|
walletManagersFacade.getOrCreateWalletManager(
|
||||||
} returns flowOf(
|
userWalletId = userWalletId,
|
||||||
setOf(
|
blockchain = Blockchain.Ethereum,
|
||||||
NetworkStatus(
|
derivationPath = token.derivationPath,
|
||||||
network = mockk {
|
)
|
||||||
every { backendId } returns "ethereum"
|
} returns walletManager
|
||||||
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
|
|
||||||
},
|
|
||||||
value = NetworkStatus.Verified(
|
|
||||||
address = mockk {
|
|
||||||
every { availableAddresses } returns addresses.map { address ->
|
|
||||||
mockk<NetworkAddress.Address> {
|
|
||||||
every { value } returns address
|
|
||||||
}
|
|
||||||
}.toSet()
|
|
||||||
},
|
|
||||||
amounts = emptyMap(),
|
|
||||||
pendingTransactions = emptyMap(),
|
|
||||||
yieldSupplyStatuses = emptyMap(),
|
|
||||||
source = StatusSource.ACTUAL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = enricher(userWalletId, response)
|
val result = enricher(userWalletId, response)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
assertThat(result.tokens).hasSize(1)
|
assertThat(result.tokens).hasSize(1)
|
||||||
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses)
|
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses.map { it.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest {
|
fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val userWalletId = UserWalletId("1234567890abcdef")
|
|
||||||
val token = createToken()
|
val token = createToken()
|
||||||
val response = createUserTokensResponse(tokens = listOf(token))
|
val response = createUserTokensResponse(tokens = listOf(token))
|
||||||
|
|
||||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.invoke(any())
|
walletManagersFacade.getOrCreateWalletManager(
|
||||||
} returns flowOf(
|
userWalletId = userWalletId,
|
||||||
setOf(
|
blockchain = Blockchain.Ethereum,
|
||||||
NetworkStatus(
|
derivationPath = token.derivationPath,
|
||||||
network = mockk {
|
)
|
||||||
every { backendId } returns "bitcoin"
|
} returns null
|
||||||
every { derivationPath.value } returns "m/44'/0'/0'/0/0"
|
|
||||||
},
|
|
||||||
value = NetworkStatus.Verified(
|
|
||||||
address = mockk {
|
|
||||||
every { availableAddresses } returns emptySet()
|
|
||||||
},
|
|
||||||
amounts = emptyMap(),
|
|
||||||
pendingTransactions = emptyMap(),
|
|
||||||
yieldSupplyStatuses = emptyMap(),
|
|
||||||
source = StatusSource.ACTUAL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
val result = enricher(userWalletId, response)
|
val result = enricher(userWalletId, response)
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
<SmellBaseline>
|
<SmellBaseline>
|
||||||
<ManuallySuppressedIssues/>
|
<ManuallySuppressedIssues/>
|
||||||
<CurrentIssues>
|
<CurrentIssues>
|
||||||
<ID>MultilineLambdaItParameter:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade${ Token( name = it.name, symbol = it.symbol, contractAddress = it.contractAddress, decimals = it.decimals, id = it.id, ) }</ID>
|
|
||||||
<ID>MultilineLambdaItParameter:UpdateWalletManagerResultFactory.kt$UpdateWalletManagerResultFactory${ createCurrencyTransaction( txHistoryItemConverter = txHistoryItemConverter, data = it, ) }</ID>
|
<ID>MultilineLambdaItParameter:UpdateWalletManagerResultFactory.kt$UpdateWalletManagerResultFactory${ createCurrencyTransaction( txHistoryItemConverter = txHistoryItemConverter, data = it, ) }</ID>
|
||||||
<ID>NamedArguments:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade$getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)</ID>
|
|
||||||
<ID>UnnecessaryLet:DefaultWalletManagersFacade.kt$DefaultWalletManagersFacade$let(txHistoryStateConverter::convert)</ID>
|
|
||||||
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$blockchain.getTestnetVersion()!!</ID>
|
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$blockchain.getTestnetVersion()!!</ID>
|
||||||
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$scanResponse.secondTwinPublicKey!!</ID>
|
<ID>UnsafeCallOnNullableType:WalletManagerFactory.kt$scanResponse.secondTwinPublicKey!!</ID>
|
||||||
</CurrentIssues>
|
</CurrentIssues>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,12 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
||||||
val blockchain = network.toBlockchain()
|
val blockchain = network.toBlockchain()
|
||||||
val derivationPath = network.derivationPath.value
|
val derivationPath = network.derivationPath.value
|
||||||
|
|
||||||
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)
|
return getAndUpdateWalletManager(
|
||||||
|
userWallet = userWallet,
|
||||||
|
blockchain = blockchain,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
extraTokens = extraTokens,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) {
|
override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) {
|
||||||
|
|
@ -123,18 +128,18 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
||||||
if (tokenInfos.isEmpty()) return
|
if (tokenInfos.isEmpty()) return
|
||||||
|
|
||||||
tokenInfos
|
tokenInfos
|
||||||
.groupBy { it.network }
|
.groupBy(TokenInfo::network)
|
||||||
.forEach { (network, tokenInfoList) ->
|
.forEach { (network, tokenInfoList) ->
|
||||||
removeTokens(
|
removeTokens(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
network = network,
|
network = network,
|
||||||
networkTokens = tokenInfoList.map {
|
networkTokens = tokenInfoList.map { tokenInfo ->
|
||||||
Token(
|
Token(
|
||||||
name = it.name,
|
name = tokenInfo.name,
|
||||||
symbol = it.symbol,
|
symbol = tokenInfo.symbol,
|
||||||
contractAddress = it.contractAddress,
|
contractAddress = tokenInfo.contractAddress,
|
||||||
decimals = it.decimals,
|
decimals = tokenInfo.decimals,
|
||||||
id = it.id,
|
id = tokenInfo.id,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -215,24 +220,24 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
||||||
"Unable to get a wallet manager for blockchain: ${currency.network}"
|
"Unable to get a wallet manager for blockchain: ${currency.network}"
|
||||||
}
|
}
|
||||||
|
|
||||||
return walletManager
|
val transactionHistoryState = walletManager.getTransactionHistoryState(
|
||||||
.getTransactionHistoryState(
|
address = walletManager.wallet.address,
|
||||||
address = walletManager.wallet.address,
|
filterType = when (currency) {
|
||||||
filterType = when (currency) {
|
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
|
||||||
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
|
is CryptoCurrency.Token -> {
|
||||||
is CryptoCurrency.Token -> {
|
val blockchainToken = Token(
|
||||||
val blockchainToken = Token(
|
name = currency.name,
|
||||||
name = currency.name,
|
symbol = currency.symbol,
|
||||||
symbol = currency.symbol,
|
contractAddress = currency.contractAddress,
|
||||||
contractAddress = currency.contractAddress,
|
decimals = currency.decimals,
|
||||||
decimals = currency.decimals,
|
id = currency.id.rawCurrencyId?.value,
|
||||||
id = currency.id.rawCurrencyId?.value,
|
)
|
||||||
)
|
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
|
||||||
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
.let(txHistoryStateConverter::convert)
|
return txHistoryStateConverter.convert(transactionHistoryState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTxHistoryItems(
|
override suspend fun getTxHistoryItems(
|
||||||
|
|
@ -366,7 +371,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
||||||
blockchain: Blockchain,
|
blockchain: Blockchain,
|
||||||
derivationPath: String?,
|
derivationPath: String?,
|
||||||
): WalletManager? {
|
): WalletManager? {
|
||||||
getWmInitializationMutex(blockchain, derivationPath).withLock {
|
getWmInitializationMutex(userWalletId, blockchain, derivationPath).withLock {
|
||||||
val userWallet = getUserWallet(userWalletId)
|
val userWallet = getUserWallet(userWalletId)
|
||||||
|
|
||||||
var walletManager = walletManagersStore.getSyncOrNull(
|
var walletManager = walletManagersStore.getSyncOrNull(
|
||||||
|
|
@ -738,15 +743,24 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
||||||
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
|
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWmInitializationMutex(blockchain: Blockchain, derivationPath: String?): Mutex {
|
private fun getWmInitializationMutex(
|
||||||
val key = createMutexMapKey(blockchain, derivationPath)
|
userWalletId: UserWalletId,
|
||||||
|
blockchain: Blockchain,
|
||||||
|
derivationPath: String?,
|
||||||
|
): Mutex {
|
||||||
|
val key = createMutexMapKey(userWalletId, blockchain, derivationPath)
|
||||||
return wmInitializationMutexes.computeIfAbsent(key) {
|
return wmInitializationMutexes.computeIfAbsent(key) {
|
||||||
Mutex()
|
Mutex()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMutexMapKey(blockchain: Blockchain, derivationPath: String?): String {
|
private fun createMutexMapKey(userWalletId: UserWalletId, blockchain: Blockchain, derivationPath: String?): String {
|
||||||
return blockchain.toNetworkId() + "|" + derivationPath
|
return listOf(
|
||||||
|
userWalletId.stringValue,
|
||||||
|
blockchain.toNetworkId(),
|
||||||
|
derivationPath,
|
||||||
|
)
|
||||||
|
.joinToString(separator = "|")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
<ID>MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } }</ID>
|
<ID>MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } }</ID>
|
||||||
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot)</ID>
|
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot)</ID>
|
||||||
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount)</ID>
|
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount)</ID>
|
||||||
<ID>SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching</ID>
|
|
||||||
<ID>SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching</ID>
|
<ID>SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching</ID>
|
||||||
<ID>UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks)</ID>
|
<ID>UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks)</ID>
|
||||||
<ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID>
|
<ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
==========================================
|
==========================================
|
||||||
Detekt Baseline Updater & Issue Counter
|
Detekt Baseline Updater & Issue Counter
|
||||||
==========================================
|
==========================================
|
||||||
Date: 2025-12-02 13:19:49
|
Date: 2025-12-02 18:48:27
|
||||||
|
|
||||||
Step 1: Running detekt to check for new issues...
|
Step 1: Running detekt to check for new issues...
|
||||||
|
|
||||||
|
|
@ -17,13 +17,13 @@ Counting issues in baseline files...
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
Summary:
|
Summary:
|
||||||
Total Issues: 1435
|
Total Issues: 1424
|
||||||
Modules with Issues: 62
|
Modules with Issues: 62
|
||||||
Average Issues per Module: 23
|
Average Issues per Module: 22
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
Fixed: 367 out of 1802 (20%)
|
Fixed: 378 out of 1802 (20%)
|
||||||
Remaining: 1435
|
Remaining: 1424
|
||||||
|
|
||||||
==========================================
|
==========================================
|
||||||
All Modules with Issues (sorted by count)
|
All Modules with Issues (sorted by count)
|
||||||
|
|
@ -31,8 +31,8 @@ All Modules with Issues (sorted by count)
|
||||||
|
|
||||||
Module Issues
|
Module Issues
|
||||||
────────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────────
|
||||||
features/wallet/impl 148
|
|
||||||
features/markets/impl 148
|
features/markets/impl 148
|
||||||
|
features/wallet/impl 146
|
||||||
features/onboarding-v2/impl 130
|
features/onboarding-v2/impl 130
|
||||||
features/swap/impl 67
|
features/swap/impl 67
|
||||||
features/send-v2/impl 57
|
features/send-v2/impl 57
|
||||||
|
|
@ -45,9 +45,9 @@ features/manage-tokens/impl 45
|
||||||
features/swap-v2/impl 40
|
features/swap-v2/impl 40
|
||||||
domain/wallets 37
|
domain/wallets 37
|
||||||
features/nft/impl 34
|
features/nft/impl 34
|
||||||
features/tester/impl 31
|
features/tester/impl 30
|
||||||
domain/tokens 28
|
|
||||||
core/ui 27
|
core/ui 27
|
||||||
|
domain/tokens 26
|
||||||
common/ui 26
|
common/ui 26
|
||||||
features/swap/domain 25
|
features/swap/domain 25
|
||||||
data/visa 22
|
data/visa 22
|
||||||
|
|
@ -55,16 +55,16 @@ features/yield-supply/impl 21
|
||||||
features/tangempay/details/impl 21
|
features/tangempay/details/impl 21
|
||||||
data/nft 20
|
data/nft 20
|
||||||
features/swap/data 15
|
features/swap/data 15
|
||||||
data/wallets 14
|
data/wallets 13
|
||||||
data/swap 13
|
data/swap 13
|
||||||
features/token-recieve/impl 11
|
features/token-recieve/impl 11
|
||||||
features/qr-scanning/impl 11
|
features/qr-scanning/impl 11
|
||||||
domain/account/status 11
|
|
||||||
data/onramp 11
|
data/onramp 11
|
||||||
core/datasource 11
|
core/datasource 11
|
||||||
data/markets 10
|
data/markets 10
|
||||||
features/details/impl 9
|
features/details/impl 9
|
||||||
domain/staking 9
|
domain/staking 9
|
||||||
|
domain/account/status 9
|
||||||
data/yield-supply 9
|
data/yield-supply 9
|
||||||
data/networks 9
|
data/networks 9
|
||||||
features/referral/impl 8
|
features/referral/impl 8
|
||||||
|
|
@ -72,7 +72,6 @@ domain/transaction 8
|
||||||
libs/tangem-sdk-api 7
|
libs/tangem-sdk-api 7
|
||||||
data/txhistory 7
|
data/txhistory 7
|
||||||
features/welcome/impl 6
|
features/welcome/impl 6
|
||||||
data/wallet-manager 6
|
|
||||||
libs/visa 5
|
libs/visa 5
|
||||||
features/send-v2/api 5
|
features/send-v2/api 5
|
||||||
features/home/impl 5
|
features/home/impl 5
|
||||||
|
|
@ -87,6 +86,7 @@ features/txhistory/impl 3
|
||||||
features/tangempay/onboarding/impl 3
|
features/tangempay/onboarding/impl 3
|
||||||
features/create-wallet-start/impl 3
|
features/create-wallet-start/impl 3
|
||||||
domain/manage-tokens 3
|
domain/manage-tokens 3
|
||||||
|
data/wallet-manager 3
|
||||||
data/manage-tokens 3
|
data/manage-tokens 3
|
||||||
common/routing 3
|
common/routing 3
|
||||||
features/account/api 2
|
features/account/api 2
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ dependencies {
|
||||||
api(projects.domain.referral)
|
api(projects.domain.referral)
|
||||||
api(projects.domain.staking)
|
api(projects.domain.staking)
|
||||||
api(projects.domain.tokens)
|
api(projects.domain.tokens)
|
||||||
|
api(projects.domain.walletManager)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
|
|
||||||
implementation(projects.libs.blockchainSdk)
|
implementation(projects.libs.blockchainSdk)
|
||||||
|
|
|
||||||
|
|
@ -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: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:ApplyTokenListSortingUseCaseV2.kt$ApplyTokenListSortingUseCaseV2${ errors[account.accountId] = it return@map account }</ID>
|
||||||
<ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID>
|
<ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID>
|
||||||
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ ExpressAsset.ID( networkId = it.network.backendId, contractAddress = (it as? CryptoCurrency.Token)?.contractAddress, ) }</ID>
|
|
||||||
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ it.network.backendId == networkId && !it.isCustom && it.contractAddress.equals(contractAddress, true) }</ID>
|
|
||||||
<ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID>
|
<ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID>
|
||||||
<ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID>
|
<ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID>
|
||||||
<ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID>
|
<ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.utils.StakingCleaner
|
import com.tangem.domain.staking.utils.StakingCleaner
|
||||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -95,6 +96,7 @@ internal object AccountStatusUseCaseModule {
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
networksCleaner: NetworksCleaner,
|
networksCleaner: NetworksCleaner,
|
||||||
|
|
@ -107,6 +109,7 @@ internal object AccountStatusUseCaseModule {
|
||||||
accountsCRUDRepository = accountsCRUDRepository,
|
accountsCRUDRepository = accountsCRUDRepository,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
networksCleaner = networksCleaner,
|
networksCleaner = networksCleaner,
|
||||||
|
|
@ -120,7 +123,6 @@ internal object AccountStatusUseCaseModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideCryptoCurrencyBalanceFetcher(
|
fun provideCryptoCurrencyBalanceFetcher(
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
|
|
@ -128,7 +130,6 @@ internal object AccountStatusUseCaseModule {
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): CryptoCurrencyBalanceFetcher {
|
): CryptoCurrencyBalanceFetcher {
|
||||||
return CryptoCurrencyBalanceFetcher(
|
return CryptoCurrencyBalanceFetcher(
|
||||||
accountsCRUDRepository = accountsCRUDRepository,
|
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ import com.tangem.domain.networks.utils.NetworksCleaner
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.utils.StakingCleaner
|
import com.tangem.domain.staking.utils.StakingCleaner
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.utils.coroutines.runSuspendCatching
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -49,6 +51,7 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
private val derivationsRepository: DerivationsRepository,
|
private val derivationsRepository: DerivationsRepository,
|
||||||
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||||
private val stakingIdFactory: StakingIdFactory,
|
private val stakingIdFactory: StakingIdFactory,
|
||||||
private val networksCleaner: NetworksCleaner,
|
private val networksCleaner: NetworksCleaner,
|
||||||
|
|
@ -83,6 +86,11 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||||
.modify(add = add, remove = remove)
|
.modify(add = add, remove = remove)
|
||||||
|
|
||||||
|
if (!modifiedCurrencyList.hasChanges) {
|
||||||
|
Timber.d("No changes in currencies, skipping")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
saveAccount(
|
saveAccount(
|
||||||
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
||||||
)
|
)
|
||||||
|
|
@ -90,17 +98,7 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||||
|
|
||||||
parallelUpdatingScope.launch {
|
parallelUpdatingScope.launch {
|
||||||
/*
|
syncTokens(userWalletId, modifiedCurrencyList)
|
||||||
* If only removal of currencies happened, we need to sync tokens. Otherwise, tokens will be synced
|
|
||||||
* when balances are refreshed for added currencies.
|
|
||||||
*/
|
|
||||||
val isOnlyRemoval = modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()
|
|
||||||
|
|
||||||
if (isOnlyRemoval) {
|
|
||||||
launch { accountsCRUDRepository.syncTokens(userWalletId) }
|
|
||||||
clearMetadata(userWalletId = userWalletId, currencies = modifiedCurrencyList.removed)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||||
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
||||||
|
|
@ -121,10 +119,10 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
|
|
||||||
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||||
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||||
.firstOrNull {
|
.firstOrNull { token ->
|
||||||
it.network.backendId == networkId &&
|
token.network.backendId == networkId &&
|
||||||
!it.isCustom &&
|
!token.isCustom &&
|
||||||
it.contractAddress.equals(contractAddress, true)
|
token.contractAddress.equals(contractAddress, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundToken != null) return@withContext foundToken
|
if (foundToken != null) return@withContext foundToken
|
||||||
|
|
@ -137,6 +135,8 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
||||||
|
|
||||||
parallelUpdatingScope.launch {
|
parallelUpdatingScope.launch {
|
||||||
|
syncTokens(userWalletId, modifiedCurrencyList)
|
||||||
|
|
||||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
||||||
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
||||||
}
|
}
|
||||||
|
|
@ -260,15 +260,40 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun syncTokens(userWalletId: UserWalletId, modifiedCurrencyList: ModifiedCurrencyList) {
|
||||||
|
createWalletManagers(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||||
|
|
||||||
|
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||||
|
.onFailure { Timber.e(it, "Failed to sync tokens for wallet $userWalletId") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||||
|
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||||
|
*
|
||||||
|
* @param userWalletId The ID of the user's wallet.
|
||||||
|
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||||
|
*/
|
||||||
|
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
|
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||||
|
|
||||||
|
for (network in networks) {
|
||||||
|
runSuspendCatching {
|
||||||
|
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||||
|
}
|
||||||
|
.onFailure { Timber.e(it, "Failed to create wallet manager for network ${network.id}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
if (currencies.isEmpty()) return
|
if (currencies.isEmpty()) return
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch {
|
launch {
|
||||||
val assetIds = currencies.mapTo(hashSetOf()) {
|
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
|
||||||
ExpressAsset.ID(
|
ExpressAsset.ID(
|
||||||
networkId = it.network.backendId,
|
networkId = currency.network.backendId,
|
||||||
contractAddress = (it as? CryptoCurrency.Token)?.contractAddress,
|
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,5 +350,8 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
val added: List<CryptoCurrency>,
|
val added: List<CryptoCurrency>,
|
||||||
val removed: List<CryptoCurrency>,
|
val removed: List<CryptoCurrency>,
|
||||||
val total: List<CryptoCurrency>,
|
val total: List<CryptoCurrency>,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
val hasChanges get() = added.isNotEmpty() || removed.isNotEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package com.tangem.domain.account.status.utils
|
package com.tangem.domain.account.status.utils
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.raise.either
|
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
|
|
@ -19,7 +17,6 @@ import timber.log.Timber
|
||||||
* Utility class responsible for fetching and refreshing the balances of various crypto currencies
|
* Utility class responsible for fetching and refreshing the balances of various crypto currencies
|
||||||
* associated with a user's wallet.
|
* associated with a user's wallet.
|
||||||
*
|
*
|
||||||
* @property accountsCRUDRepository Repository for managing account data.
|
|
||||||
* @property multiNetworkStatusFetcher Fetcher for updating network statuses.
|
* @property multiNetworkStatusFetcher Fetcher for updating network statuses.
|
||||||
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
|
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
|
||||||
* @property multiYieldBalanceFetcher Fetcher for updating yield balances.
|
* @property multiYieldBalanceFetcher Fetcher for updating yield balances.
|
||||||
|
|
@ -29,7 +26,6 @@ import timber.log.Timber
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
class CryptoCurrencyBalanceFetcher(
|
class CryptoCurrencyBalanceFetcher(
|
||||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
|
||||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
|
|
@ -80,22 +76,13 @@ class CryptoCurrencyBalanceFetcher(
|
||||||
private suspend fun refreshNetworks(
|
private suspend fun refreshNetworks(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
currencies: List<CryptoCurrency>,
|
currencies: List<CryptoCurrency>,
|
||||||
): Either<Throwable, Unit> = either {
|
): Either<Throwable, Unit> {
|
||||||
val either = multiNetworkStatusFetcher(
|
return multiNetworkStatusFetcher(
|
||||||
params = MultiNetworkStatusFetcher.Params(
|
params = MultiNetworkStatusFetcher.Params(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network),
|
networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
arrow.core.raise.catch(
|
|
||||||
block = { accountsCRUDRepository.syncTokens(userWalletId) },
|
|
||||||
catch = {
|
|
||||||
Timber.e(it, "Failed to sync tokens for wallet: $userWalletId")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return either
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshYieldBalances(
|
private suspend fun refreshYieldBalances(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ dependencies {
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
|
|
||||||
|
implementation(tangemDeps.blockchain)
|
||||||
|
|
||||||
/* Core */
|
/* Core */
|
||||||
api(projects.core.pagination)
|
api(projects.core.pagination)
|
||||||
testImplementation(projects.core.pagination)
|
testImplementation(projects.core.pagination)
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ class SaveManagedTokensUseCase(
|
||||||
|
|
||||||
parallelUpdatingScope.launch {
|
parallelUpdatingScope.launch {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
|
syncTokens(userWalletId = userWalletId, addedCurrencies = savedCurrencies)
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
refreshUpdatedNetworks(
|
refreshUpdatedNetworks(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
|
|
@ -96,6 +98,26 @@ class SaveManagedTokensUseCase(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||||
|
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||||
|
currenciesRepository.syncTokens(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||||
|
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||||
|
*
|
||||||
|
* @param userWalletId The ID of the user's wallet.
|
||||||
|
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||||
|
*/
|
||||||
|
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
|
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||||
|
|
||||||
|
for (network in networks) {
|
||||||
|
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||||
multiNetworkStatusFetcher(
|
multiNetworkStatusFetcher(
|
||||||
MultiNetworkStatusFetcher.Params(
|
MultiNetworkStatusFetcher.Params(
|
||||||
|
|
@ -103,8 +125,6 @@ class SaveManagedTokensUseCase(
|
||||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
currenciesRepository.syncTokens(userWalletId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshUpdatedYieldBalances(
|
private suspend fun refreshUpdatedYieldBalances(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ dependencies {
|
||||||
api(projects.domain.networks)
|
api(projects.domain.networks)
|
||||||
api(projects.domain.staking)
|
api(projects.domain.staking)
|
||||||
api(projects.domain.quotes)
|
api(projects.domain.quotes)
|
||||||
|
api(projects.domain.walletManager)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
api(projects.domain.wallets.models)
|
api(projects.domain.wallets.models)
|
||||||
api(projects.domain.promo)
|
api(projects.domain.promo)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
|
@ -31,6 +32,7 @@ import kotlinx.coroutines.withContext
|
||||||
class SaveMarketTokensUseCase(
|
class SaveMarketTokensUseCase(
|
||||||
private val derivationsRepository: DerivationsRepository,
|
private val derivationsRepository: DerivationsRepository,
|
||||||
private val marketsTokenRepository: MarketsTokenRepository,
|
private val marketsTokenRepository: MarketsTokenRepository,
|
||||||
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
|
|
@ -80,6 +82,8 @@ class SaveMarketTokensUseCase(
|
||||||
|
|
||||||
parallelUpdatingScope.launch {
|
parallelUpdatingScope.launch {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
|
syncTokens(userWalletId, savedCurrencies)
|
||||||
|
|
||||||
launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) }
|
launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) }
|
||||||
launch { refreshUpdatedYieldBalances(userWalletId, savedCurrencies) }
|
launch { refreshUpdatedYieldBalances(userWalletId, savedCurrencies) }
|
||||||
launch { refreshUpdatedQuotes(savedCurrencies) }
|
launch { refreshUpdatedQuotes(savedCurrencies) }
|
||||||
|
|
@ -88,6 +92,26 @@ class SaveMarketTokensUseCase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||||
|
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||||
|
currenciesRepository.syncTokens(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||||
|
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||||
|
*
|
||||||
|
* @param userWalletId The ID of the user's wallet.
|
||||||
|
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||||
|
*/
|
||||||
|
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
|
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||||
|
|
||||||
|
for (network in networks) {
|
||||||
|
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||||
multiNetworkStatusFetcher(
|
multiNetworkStatusFetcher(
|
||||||
MultiNetworkStatusFetcher.Params(
|
MultiNetworkStatusFetcher.Params(
|
||||||
|
|
@ -95,7 +119,6 @@ class SaveMarketTokensUseCase(
|
||||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
currenciesRepository.syncTokens(userWalletId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshUpdatedYieldBalances(
|
private suspend fun refreshUpdatedYieldBalances(
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@
|
||||||
<ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred<AssetRequirementsCondition?>?</ID>
|
<ID>CanBeNonNullable:BaseActionsFactory.kt$BaseActionsFactory$requirementsDeferred: Deferred<AssetRequirementsCondition?>?</ID>
|
||||||
<ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred<ScenarioUnavailabilityReason>?</ID>
|
<ID>CanBeNonNullable:CommonActionsFactory.kt$CommonActionsFactory$swapUnavailableReasonDeferred: Deferred<ScenarioUnavailabilityReason>?</ID>
|
||||||
<ID>ExplicitCollectionElementAccessMethod:GetWalletTotalBalanceUseCase.kt$GetWalletTotalBalanceUseCase$walletBalanceCache.put(userWalletId, content)</ID>
|
<ID>ExplicitCollectionElementAccessMethod:GetWalletTotalBalanceUseCase.kt$GetWalletTotalBalanceUseCase$walletBalanceCache.put(userWalletId, content)</ID>
|
||||||
<ID>MultilineLambdaItParameter:AddCryptoCurrenciesUseCase.kt$AddCryptoCurrenciesUseCase${ it.network.backendId == networkId && !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${ singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(rawCurrencyId = it)) .firstOrNull() }</ID>
|
||||||
<ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ val exception = IllegalStateException("$it") Error.DataError(exception) }</ID>
|
<ID>MultilineLambdaItParameter:BaseCurrencyStatusOperations.kt$BaseCurrencyStatusOperations${ val exception = IllegalStateException("$it") Error.DataError(exception) }</ID>
|
||||||
<ID>MultilineLambdaItParameter:FetchCurrencyStatusUseCase.kt$FetchCurrencyStatusUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it")) StakingIdFactory.Error.UnsupportedCurrency -> Unit.right() } return@either }</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.StakingIdFactory
|
||||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
@ -28,6 +29,7 @@ import kotlinx.coroutines.coroutineScope
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
class AddCryptoCurrenciesUseCase(
|
class AddCryptoCurrenciesUseCase(
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||||
|
|
@ -73,9 +75,11 @@ class AddCryptoCurrenciesUseCase(
|
||||||
)
|
)
|
||||||
val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either
|
val currencyToAdd = currency.takeUnless(existingCurrencies::contains) ?: return@either
|
||||||
|
|
||||||
addCurrencies(userWalletId, currencyToAdd)
|
val addedCurrencies = addCurrencies(userWalletId, currencyToAdd)
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
|
syncTokens(userWalletId, addedCurrencies)
|
||||||
|
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) },
|
async { refreshUpdatedNetworks(userWalletId, currencyToAdd, existingCurrencies) },
|
||||||
async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) },
|
async { refreshUpdatedYieldBalances(userWalletId, currencyToAdd) },
|
||||||
|
|
@ -102,18 +106,20 @@ class AddCryptoCurrenciesUseCase(
|
||||||
|
|
||||||
val foundToken = existingCurrencies
|
val foundToken = existingCurrencies
|
||||||
.filterIsInstance<CryptoCurrency.Token>()
|
.filterIsInstance<CryptoCurrency.Token>()
|
||||||
.firstOrNull {
|
.firstOrNull { token ->
|
||||||
it.network.backendId == networkId &&
|
token.network.backendId == networkId &&
|
||||||
!it.isCustom &&
|
!token.isCustom &&
|
||||||
it.contractAddress.equals(contractAddress, true)
|
token.contractAddress.equals(contractAddress, true)
|
||||||
}
|
}
|
||||||
if (foundToken != null) {
|
if (foundToken != null) {
|
||||||
return@either foundToken
|
return@either foundToken
|
||||||
}
|
}
|
||||||
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
|
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
|
||||||
addCurrencies(userWalletId, tokenToAdd)
|
val addedCurrencies = addCurrencies(userWalletId, tokenToAdd)
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
|
syncTokens(userWalletId = userWalletId, addedCurrencies = addedCurrencies)
|
||||||
|
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) },
|
async { refreshUpdatedNetworks(userWalletId, tokenToAdd, existingCurrencies) },
|
||||||
async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) },
|
async { refreshUpdatedYieldBalances(userWalletId, tokenToAdd) },
|
||||||
|
|
@ -124,6 +130,26 @@ class AddCryptoCurrenciesUseCase(
|
||||||
tokenToAdd
|
tokenToAdd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||||
|
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||||
|
currenciesRepository.syncTokens(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||||
|
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||||
|
*
|
||||||
|
* @param userWalletId The ID of the user's wallet.
|
||||||
|
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||||
|
*/
|
||||||
|
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
|
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||||
|
|
||||||
|
for (network in networks) {
|
||||||
|
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the network statuses for tokens that have corresponding coins in the
|
* Refreshes the network statuses for tokens that have corresponding coins in the
|
||||||
* [existingCurrencies] list.
|
* [existingCurrencies] list.
|
||||||
|
|
@ -149,8 +175,6 @@ class AddCryptoCurrenciesUseCase(
|
||||||
networks = setOfNotNull(networksToUpdate, networkToUpdate),
|
networks = setOfNotNull(networksToUpdate, networkToUpdate),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
currenciesRepository.syncTokens(userWalletId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshUpdatedYieldBalances(
|
private suspend fun refreshUpdatedYieldBalances(
|
||||||
|
|
@ -162,9 +186,9 @@ class AddCryptoCurrenciesUseCase(
|
||||||
currencyId = addedCurrency.id,
|
currencyId = addedCurrency.id,
|
||||||
network = addedCurrency.network,
|
network = addedCurrency.network,
|
||||||
)
|
)
|
||||||
.getOrElse {
|
.getOrElse { error ->
|
||||||
when (it) {
|
when (error) {
|
||||||
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it"))
|
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$error"))
|
||||||
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
|
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,12 +229,14 @@ class AddCryptoCurrenciesUseCase(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Throwable>.addCurrencies(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
private suspend fun Raise<Throwable>.addCurrencies(
|
||||||
catch(
|
userWalletId: UserWalletId,
|
||||||
{ currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) },
|
currency: CryptoCurrency,
|
||||||
) {
|
): List<CryptoCurrency> {
|
||||||
raise(it)
|
return catch(
|
||||||
}
|
block = { currenciesRepository.addCurrenciesCache(userWalletId, listOf(currency)) },
|
||||||
|
catch = ::raise,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
<ID>ExplicitCollectionElementAccessMethod:TestPushAddKeyDataTransformer.kt$TestPushAddKeyDataTransformer$mutableData.set(index = index, updated)</ID>
|
<ID>ExplicitCollectionElementAccessMethod:TestPushAddKeyDataTransformer.kt$TestPushAddKeyDataTransformer$mutableData.set(index = index, updated)</ID>
|
||||||
<ID>ExplicitCollectionElementAccessMethod:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</ID>
|
<ID>ExplicitCollectionElementAccessMethod:TestPushAddValueDataTransformer.kt$TestPushAddValueDataTransformer$mutableData.set(index = index, updated)</ID>
|
||||||
<ID>MaxChainedCallsOnSameLine:BlockchainProvidersScreen.kt$ProvidersDnDTarget$event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()</ID>
|
<ID>MaxChainedCallsOnSameLine:BlockchainProvidersScreen.kt$ProvidersDnDTarget$event.toAndroidDragEvent().clipData.getItemAt(0).text.toString().toInt()</ID>
|
||||||
<ID>MultilineLambdaItParameter:ApiEnvironmentComparator.kt$ApiEnvironmentComparator${ when (it) { ApiEnvironment.DEV -> 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:BlockchainProvidersScreen.kt${ value = it state.onValueChange(it.text) }</ID>
|
||||||
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ if (it.blockchainId == blockchainId) { it.update() } else { it } }</ID>
|
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ if (it.blockchainId == blockchainId) { it.update() } else { it } }</ID>
|
||||||
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ it.copyProvidersUM(blockchainId = id) { copy( addPublicProviderDialog = addPublicProviderDialog.copy( hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), ), ) } }</ID>
|
<ID>MultilineLambdaItParameter:BlockchainProvidersViewModel.kt$BlockchainProvidersViewModel${ it.copyProvidersUM(blockchainId = id) { copy( addPublicProviderDialog = addPublicProviderDialog.copy( hasError = !PatternsCompat.WEB_URL.matcher(url).matches(), ), ) } }</ID>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@
|
||||||
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ Timber.e( """ Unable to get user wallet |- ID: $userWalletId |- Exception: $it """.trimIndent(), ) null }</ID>
|
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ Timber.e( """ Unable to get user wallet |- ID: $userWalletId |- Exception: $it """.trimIndent(), ) null }</ID>
|
||||||
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ router.openOnboardingScreen( scanResponse = it.scanResponse, continueBackup = true, ) }</ID>
|
<ID>MultilineLambdaItParameter:WalletWarningsClickIntents.kt$WalletWarningsClickIntentsImplementor${ router.openOnboardingScreen( scanResponse = it.scanResponse, continueBackup = true, ) }</ID>
|
||||||
<ID>MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() }</ID>
|
<ID>MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() }</ID>
|
||||||
<ID>MultilineLambdaItParameter:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) { it.walletCardState.id } else { null } }</ID>
|
|
||||||
<ID>NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)</ID>
|
<ID>NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)</ID>
|
||||||
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)</ID>
|
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)</ID>
|
||||||
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)</ID>
|
<ID>NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)</ID>
|
||||||
|
|
@ -109,7 +108,6 @@
|
||||||
<ID>NoNameShadowing:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ it is TokensListItemUM.Token }</ID>
|
<ID>NoNameShadowing:WalletCurrencyActionsClickIntents.kt$WalletCurrencyActionsClickIntentsImplementor${ it is TokensListItemUM.Token }</ID>
|
||||||
<ID>NoNameShadowing:WalletNFTItem.kt$modifier</ID>
|
<ID>NoNameShadowing:WalletNFTItem.kt$modifier</ID>
|
||||||
<ID>NoNameShadowing:WalletScreen.kt${ it.organizeTokensButtonConfig?.let { config -> organizeTokensButton( modifier = itemModifier, isEnabled = config.isEnabled, onClick = config.onClick, ) } }</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$$bitcoinCurrency</ID>
|
||||||
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID>
|
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$$bitcoinStatus</ID>
|
||||||
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID>
|
<ID>NullableToStringCall:DefaultPromoDeeplinkHandler.kt$DefaultPromoDeeplinkHandler$${cryptoCurrencies?.size}</ID>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue