Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-02 13:23:56 +04:00
parent c9be7fa5c5
commit d47433ee6c
24 changed files with 193 additions and 90 deletions

View file

@ -6,8 +6,6 @@
<ID>MultilineLambdaItParameter:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository${ if (it is HttpException &amp;&amp; it.code == HttpException.Code.NOT_MODIFIED) { null } else { throw it } }</ID> <ID>MultilineLambdaItParameter:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository${ if (it is HttpException &amp;&amp; it.code == HttpException.Code.NOT_MODIFIED) { null } else { throw it } }</ID>
<ID>MultilineLambdaItParameter:GetWalletAccountsResponseExt.kt${ enrichedTokensByAccountId[it].orEmpty().map { token -&gt; // Tokens from unexisting accounts should be copied to the main account token.copy(accountId = accountDTO.id) } }</ID> <ID>MultilineLambdaItParameter:GetWalletAccountsResponseExt.kt${ enrichedTokensByAccountId[it].orEmpty().map { token -&gt; // Tokens from unexisting accounts should be copied to the main account token.copy(accountId = accountDTO.id) } }</ID>
<ID>NoNameShadowing:GetWalletAccountsResponseExt.kt$tokens</ID> <ID>NoNameShadowing:GetWalletAccountsResponseExt.kt$tokens</ID>
<ID>NullableToStringCall:AccountListCryptoCurrenciesProducer.kt$AccountListCryptoCurrenciesProducer$${this::class.simpleName}</ID>
<ID>NullableToStringCall:DefaultMultiWalletCryptoCurrenciesProducer.kt$DefaultMultiWalletCryptoCurrenciesProducer$${this::class.simpleName}</ID>
<ID>UnnecessaryLet:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository$let(AccountName::invoke)</ID> <ID>UnnecessaryLet:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository$let(AccountName::invoke)</ID>
</CurrentIssues> </CurrentIssues>
</SmellBaseline> </SmellBaseline>

View file

@ -36,11 +36,12 @@ internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor(
override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some() override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some()
@Suppress("NullableToStringCall")
override fun produce(): Flow<Set<CryptoCurrency>> { override fun produce(): Flow<Set<CryptoCurrency>> {
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
if (!userWallet.isMultiCurrency) { if (!userWallet.isMultiCurrency) {
error("${this::class.simpleName} supports only multi-currency wallet") error("${this::class.simpleName ?: this::class.toString()} supports only multi-currency wallet")
} }
return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data
@ -49,10 +50,13 @@ internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor(
if (response == null) return@map emptySet() if (response == null) return@map emptySet()
response.accounts.flatMapTo(hashSetOf()) { accountDTO -> response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
?: return@map emptySet()
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
tokens = accountDTO.tokens.orEmpty(), tokens = accountDTO.tokens.orEmpty(),
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), accountIndex = accountIndex,
) )
} }
} }

View file

@ -5,6 +5,7 @@ import arrow.core.some
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.local.token.UserTokensResponseStore 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.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
@ -39,7 +40,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
if (!userWallet.isMultiCurrency) { if (!userWallet.isMultiCurrency) {
error("${this::class.simpleName} supports only multi-currency wallet") error("${this::class.simpleName ?: this::class.toString()} supports only multi-currency wallet")
} }
return userTokensResponseStore.get(userWalletId = params.userWalletId) return userTokensResponseStore.get(userWalletId = params.userWalletId)
@ -50,6 +51,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = response, response = response,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
).toSet() ).toSet()
} }
.onEmpty { emit(emptySet()) } .onEmpty { emit(emptySet()) }

View file

@ -10,6 +10,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore 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.card.configs.GenericCardConfig import com.tangem.domain.card.configs.GenericCardConfig
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
@ -72,7 +73,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
} }
verify(inverse = true) { verify(inverse = true) {
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
} }
} }
@ -115,6 +116,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} returns cryptoCurrencies.toList() } returns cryptoCurrencies.toList()
@ -122,6 +124,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = updatedUserTokensResponse, response = updatedUserTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} returns updatedCryptoCurrencies.toList() } returns updatedCryptoCurrencies.toList()
@ -144,6 +147,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
@ -162,6 +166,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = updatedUserTokensResponse, response = updatedUserTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
} }
@ -186,6 +191,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} returns cryptoCurrencies.toList() } returns cryptoCurrencies.toList()
@ -208,6 +214,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
@ -252,6 +259,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} returns cryptoCurrencies.toList() } returns cryptoCurrencies.toList()
@ -283,6 +291,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = userTokensResponse, response = userTokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
} }
@ -307,7 +316,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
} }
verify(inverse = true) { verify(inverse = true) {
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
} }
} }
@ -335,7 +344,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
verify(inverse = true) { verify(inverse = true) {
userTokensResponseStore.get(any()) userTokensResponseStore.get(any())
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
} }
} }

View file

@ -144,6 +144,9 @@ internal class DefaultCardCryptoCurrencyFactory(
?: return emptyMap() ?: return emptyMap()
response.accounts.flatMapTo(hashSetOf()) { accountDTO -> response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
?: return@flatMapTo emptySet()
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
tokens = accountDTO.tokens.orEmpty().filter { token -> tokens = accountDTO.tokens.orEmpty().filter { token ->
networks.any { networks.any {
@ -151,7 +154,7 @@ internal class DefaultCardCryptoCurrencyFactory(
} }
}, },
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), accountIndex = accountIndex,
) )
} }
} else { } else {
@ -163,6 +166,7 @@ internal class DefaultCardCryptoCurrencyFactory(
networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath } networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath }
}, },
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
.groupBy(CryptoCurrency::network) .groupBy(CryptoCurrency::network)
@ -181,10 +185,13 @@ internal class DefaultCardCryptoCurrencyFactory(
?: return emptyMap() ?: return emptyMap()
response.accounts.flatMapTo(hashSetOf()) { accountDTO -> response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
?: return@flatMapTo emptySet()
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
tokens = accountDTO.tokens.orEmpty().filter { token -> token.networkId in networkIds }, tokens = accountDTO.tokens.orEmpty().filter { token -> token.networkId in networkIds },
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), accountIndex = accountIndex,
) )
} }
} else { } else {
@ -194,6 +201,7 @@ internal class DefaultCardCryptoCurrencyFactory(
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
tokens = response.tokens.filter { token -> token.networkId in networkIds }, tokens = response.tokens.filter { token -> token.networkId in networkIds },
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
.groupBy { it.network.id.rawId } .groupBy { it.network.id.rawId }

View file

@ -22,7 +22,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
fun createCurrencies( fun createCurrencies(
response: UserTokensResponse, response: UserTokensResponse,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex? = null, accountIndex: DerivationIndex,
): List<CryptoCurrency> { ): List<CryptoCurrency> {
return createCurrencies(tokens = response.tokens, userWallet = userWallet, accountIndex = accountIndex) return createCurrencies(tokens = response.tokens, userWallet = userWallet, accountIndex = accountIndex)
} }
@ -30,7 +30,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
fun createCurrencies( fun createCurrencies(
tokens: List<UserTokensResponse.Token>, tokens: List<UserTokensResponse.Token>,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex? = null, accountIndex: DerivationIndex,
): List<CryptoCurrency> { ): List<CryptoCurrency> {
return tokens return tokens
.asSequence() .asSequence()
@ -42,7 +42,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
fun createCurrency( fun createCurrency(
responseToken: UserTokensResponse.Token, responseToken: UserTokensResponse.Token,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex? = null, accountIndex: DerivationIndex,
): CryptoCurrency? { ): CryptoCurrency? {
var blockchain = Blockchain.fromNetworkId(responseToken.networkId) var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) { if (blockchain == null || blockchain == Blockchain.Unknown) {
@ -103,7 +103,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
blockchain: Blockchain, blockchain: Blockchain,
responseToken: UserTokensResponse.Token, responseToken: UserTokensResponse.Token,
network: Network, network: Network,
): CryptoCurrency.Coin? { ): CryptoCurrency.Coin {
return CryptoCurrency.Coin( return CryptoCurrency.Coin(
id = getCoinId(network, blockchain.toCoinId()), id = getCoinId(network, blockchain.toCoinId()),
network = network, network = network,
@ -127,7 +127,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
} }
} }
private fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token? { private fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token {
val id = getTokenId(network, sdkToken) val id = getTokenId(network, sdkToken)
return CryptoCurrency.Token( return CryptoCurrency.Token(

View file

@ -3,15 +3,7 @@
<ManuallySuppressedIssues/> <ManuallySuppressedIssues/>
<CurrentIssues> <CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository${ // TODO: refactor https://tangem.atlassian.net/browse/AND-10006\ if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { return@mapNotNull null } networkFactory.create( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) }</ID> <ID>MultilineLambdaItParameter:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository${ // TODO: refactor https://tangem.atlassian.net/browse/AND-10006\ if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { return@mapNotNull null } networkFactory.create( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultManageTokensRepository.kt$DefaultManageTokensRepository${ it.contractAddress != null &amp;&amp; it.networkId == network.backendId &amp;&amp; it.derivationPath == network.derivationPath.value }</ID>
<ID>MultilineLambdaItParameter:ManageTokensUpdateFetcher.kt$ManageTokensUpdateFetcher${ if (it.key == toUpdate[index].key) { Batch(it.key, updatedItems) } else { null } }</ID> <ID>MultilineLambdaItParameter:ManageTokensUpdateFetcher.kt$ManageTokensUpdateFetcher${ if (it.key == toUpdate[index].key) { Batch(it.key, updatedItems) } else { null } }</ID>
<ID>NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$create(coinsResponse, tokensResponse, userWallet, accountIndex)</ID>
<ID>NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$createToken(coin, tokensResponse, coinsResponse.imageHost, userWallet, accountIndex)</ID>
<ID>NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$findAddedInNetworks(coinResponse.id, tokensResponse, userWallet, accountIndex)</ID>
<ID>NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$findAddedInNetworks(testnetToken.id, tokensResponse, userWallet, accountIndex)</ID>
<ID>SuspendFunSwallowedCancellation:DefaultManageTokensRepository.kt$DefaultManageTokensRepository$runCatching</ID>
<ID>UnsafeCallOnNullableType:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository$coinNetwork.decimalCount!!</ID> <ID>UnsafeCallOnNullableType:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository$coinNetwork.decimalCount!!</ID>
<ID>UseOrEmpty:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$testnetToken.networks?.mapNotNull { network -&gt; createSource( networkId = network.id, contractAddress = network.address, decimals = network.decimalCount, userWallet = userWallet, accountIndex = accountIndex, ) } ?: emptyList()</ID>
<ID>UseOrEmpty:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$tokensResponse ?.let { createCustomTokens(it, userWallet, accountIndex) } ?: emptyList()</ID>
</CurrentIssues> </CurrentIssues>
</SmellBaseline> </SmellBaseline>

View file

@ -41,6 +41,7 @@ import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request
import com.tangem.pagination.toBatchFlow import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
@Suppress("LongParameterList", "LargeClass") @Suppress("LongParameterList", "LargeClass")
internal class DefaultManageTokensRepository( internal class DefaultManageTokensRepository(
@ -176,7 +177,7 @@ internal class DefaultManageTokensRepository(
val shouldFetch = loadUserTokensFromRemote && userWallet != null val shouldFetch = loadUserTokensFromRemote && userWallet != null
val fetchedResponse = if (shouldFetch) { val fetchedResponse = if (shouldFetch) {
runCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull() runSuspendCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull()
} else { } else {
null null
} }
@ -214,19 +215,22 @@ internal class DefaultManageTokensRepository(
userWallet != null && userWallet != null &&
query == null query == null
val accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull()
?: return emptyList()
val items = if (isCreateWithCustom) { val items = if (isCreateWithCustom) {
managedCryptoCurrencyFactory.createWithCustomTokens( managedCryptoCurrencyFactory.createWithCustomTokens(
coinsResponse = updatedCoinsResponse, coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), accountIndex = accountIndex,
) )
} else { } else {
managedCryptoCurrencyFactory.create( managedCryptoCurrencyFactory.create(
coinsResponse = updatedCoinsResponse, coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), accountIndex = accountIndex,
) )
} }
@ -262,14 +266,14 @@ internal class DefaultManageTokensRepository(
coinsResponse = updatedCoinsResponse, coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = null, accountIndex = DerivationIndex.Main,
) )
} else { } else {
managedCryptoCurrencyFactory.create( managedCryptoCurrencyFactory.create(
coinsResponse = updatedCoinsResponse, coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = null, accountIndex = DerivationIndex.Main,
) )
} }
} }
@ -307,6 +311,13 @@ internal class DefaultManageTokensRepository(
) )
} }
val accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull()
?: return BatchFetchResult.Success(
data = emptyList(),
empty = true,
last = true,
)
val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens( val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens(
testnetTokensConfig = if (!searchText.isNullOrBlank()) { testnetTokensConfig = if (!searchText.isNullOrBlank()) {
testnetTokensConfig.copy( testnetTokensConfig.copy(
@ -320,7 +331,7 @@ internal class DefaultManageTokensRepository(
}, },
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), accountIndex = accountIndex,
) )
return BatchFetchResult.Success( return BatchFetchResult.Success(
@ -350,7 +361,7 @@ internal class DefaultManageTokensRepository(
}, },
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId), tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
userWallet = userWallet, userWallet = userWallet,
accountIndex = null, accountIndex = DerivationIndex.Main,
) )
return BatchFetchResult.Success( return BatchFetchResult.Success(
@ -392,10 +403,10 @@ internal class DefaultManageTokensRepository(
) )
val newTokensList = storedTokens.tokens + addedTokens - removedTokens.toSet() val newTokensList = storedTokens.tokens + addedTokens - removedTokens.toSet()
return newTokensList.any { return newTokensList.any { token ->
it.contractAddress != null && token.contractAddress != null &&
it.networkId == network.backendId && token.networkId == network.backendId &&
it.derivationPath == network.derivationPath.value token.derivationPath == network.derivationPath.value
} }
} }

View file

@ -35,10 +35,16 @@ internal class ManagedCryptoCurrencyFactory(
coinsResponse: CoinsResponse, coinsResponse: CoinsResponse,
tokensResponse: UserTokensResponse?, tokensResponse: UserTokensResponse?,
userWallet: UserWallet?, userWallet: UserWallet?,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): List<ManagedCryptoCurrency> { ): List<ManagedCryptoCurrency> {
return coinsResponse.coins.mapNotNull { coin -> return coinsResponse.coins.mapNotNull { coin ->
createToken(coin, tokensResponse, coinsResponse.imageHost, userWallet, accountIndex) createToken(
coinResponse = coin,
tokensResponse = tokensResponse,
imageHost = coinsResponse.imageHost,
userWallet = userWallet,
accountIndex = accountIndex,
)
} }
} }
@ -46,10 +52,15 @@ internal class ManagedCryptoCurrencyFactory(
coinsResponse: CoinsResponse, coinsResponse: CoinsResponse,
tokensResponse: UserTokensResponse, tokensResponse: UserTokensResponse,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): List<ManagedCryptoCurrency> { ): List<ManagedCryptoCurrency> {
val customTokens = createCustomTokens(tokensResponse, userWallet, accountIndex) val customTokens = createCustomTokens(tokensResponse, userWallet, accountIndex)
val tokens = create(coinsResponse, tokensResponse, userWallet, accountIndex) val tokens = create(
coinsResponse = coinsResponse,
tokensResponse = tokensResponse,
userWallet = userWallet,
accountIndex = accountIndex,
)
return customTokens + tokens return customTokens + tokens
} }
@ -58,11 +69,11 @@ internal class ManagedCryptoCurrencyFactory(
testnetTokensConfig: TestnetTokensConfig, testnetTokensConfig: TestnetTokensConfig,
tokensResponse: UserTokensResponse?, tokensResponse: UserTokensResponse?,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): List<ManagedCryptoCurrency> { ): List<ManagedCryptoCurrency> {
val customTokens = tokensResponse val customTokens = tokensResponse
?.let { createCustomTokens(it, userWallet, accountIndex) } ?.let { createCustomTokens(it, userWallet, accountIndex) }
?: emptyList() .orEmpty()
val testnetTokens = testnetTokensConfig.tokens.map { testnetToken -> val testnetTokens = testnetTokensConfig.tokens.map { testnetToken ->
ManagedCryptoCurrency.Token( ManagedCryptoCurrency.Token(
id = ManagedCryptoCurrency.ID(testnetToken.id), id = ManagedCryptoCurrency.ID(testnetToken.id),
@ -77,8 +88,13 @@ internal class ManagedCryptoCurrencyFactory(
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountIndex, accountIndex = accountIndex,
) )
} ?: emptyList(), }.orEmpty(),
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, userWallet, accountIndex), addedIn = findAddedInNetworks(
currencyId = testnetToken.id,
tokensResponse = tokensResponse,
userWallet = userWallet,
accountIndex = accountIndex,
),
) )
} }
@ -88,7 +104,7 @@ internal class ManagedCryptoCurrencyFactory(
private fun createCustomTokens( private fun createCustomTokens(
tokensResponse: UserTokensResponse, tokensResponse: UserTokensResponse,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): List<ManagedCryptoCurrency> = tokensResponse.tokens ): List<ManagedCryptoCurrency> = tokensResponse.tokens
.mapNotNull { token -> .mapNotNull { token ->
maybeCreateCustomToken(token, userWallet, accountIndex) maybeCreateCustomToken(token, userWallet, accountIndex)
@ -97,7 +113,7 @@ internal class ManagedCryptoCurrencyFactory(
private fun maybeCreateCustomToken( private fun maybeCreateCustomToken(
token: UserTokensResponse.Token, token: UserTokensResponse.Token,
userWallet: UserWallet, userWallet: UserWallet,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): ManagedCryptoCurrency? { ): ManagedCryptoCurrency? {
val blockchain = Blockchain.fromNetworkId(token.networkId) val blockchain = Blockchain.fromNetworkId(token.networkId)
?.takeUnless { it in excludedBlockchains } ?.takeUnless { it in excludedBlockchains }
@ -161,7 +177,7 @@ internal class ManagedCryptoCurrencyFactory(
tokensResponse: UserTokensResponse?, tokensResponse: UserTokensResponse?,
imageHost: String?, imageHost: String?,
userWallet: UserWallet?, userWallet: UserWallet?,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): ManagedCryptoCurrency? { ): ManagedCryptoCurrency? {
if (coinResponse.networks.isEmpty() || !coinResponse.active) return null if (coinResponse.networks.isEmpty() || !coinResponse.active) return null
@ -184,7 +200,12 @@ internal class ManagedCryptoCurrencyFactory(
symbol = coinResponse.symbol, symbol = coinResponse.symbol,
iconUrl = getIconUrl(coinResponse.id, imageHost), iconUrl = getIconUrl(coinResponse.id, imageHost),
availableNetworks = availableNetworks, availableNetworks = availableNetworks,
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, userWallet, accountIndex), addedIn = findAddedInNetworks(
currencyId = coinResponse.id,
tokensResponse = tokensResponse,
userWallet = userWallet,
accountIndex = accountIndex,
),
) )
} }
@ -194,7 +215,7 @@ internal class ManagedCryptoCurrencyFactory(
decimals: Int?, decimals: Int?,
userWallet: UserWallet?, userWallet: UserWallet?,
extraDerivationPath: String? = null, extraDerivationPath: String? = null,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): SourceNetwork? { ): SourceNetwork? {
val blockchain = Blockchain.fromNetworkId(networkId) val blockchain = Blockchain.fromNetworkId(networkId)
?.takeUnless { it in excludedBlockchains } ?.takeUnless { it in excludedBlockchains }
@ -235,7 +256,7 @@ internal class ManagedCryptoCurrencyFactory(
currencyId: String, currencyId: String,
tokensResponse: UserTokensResponse?, tokensResponse: UserTokensResponse?,
userWallet: UserWallet?, userWallet: UserWallet?,
accountIndex: DerivationIndex?, accountIndex: DerivationIndex,
): Set<Network> { ): Set<Network> {
if (tokensResponse == null) return emptySet() if (tokensResponse == null) return emptySet()

View file

@ -44,6 +44,7 @@ dependencies {
/** Libs */ /** Libs */
implementation(projects.libs.blockchainSdk) implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
/** Other */ /** Other */
implementation(deps.androidx.datastore) implementation(deps.androidx.datastore)

View file

@ -1,12 +1,16 @@
package com.tangem.data.swap.converter.transaction package com.tangem.data.swap.converter.transaction
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.swap.models.SwapStatusDTO import com.tangem.data.swap.models.SwapStatusDTO
import com.tangem.data.swap.models.SwapTransactionDTO import com.tangem.data.swap.models.SwapTransactionDTO
import com.tangem.data.swap.models.SwapTxTypeDTO import com.tangem.data.swap.models.SwapTxTypeDTO
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapTransactionModel import com.tangem.domain.swap.models.SwapTransactionModel
import com.tangem.domain.swap.models.SwapTxType import com.tangem.domain.swap.models.SwapTxType
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
import com.tangem.utils.converter.TwoWayConverter import com.tangem.utils.converter.TwoWayConverter
internal class SavedSwapTransactionConverter( internal class SavedSwapTransactionConverter(
@ -48,9 +52,23 @@ internal class SavedSwapTransactionConverter(
): SwapTransactionModel { ): SwapTransactionModel {
val status = txStatuses[value.txId] val status = txStatuses[value.txId]
val refundCurrency = status?.refundTokensResponse?.let { id -> val refundCurrency = status?.refundTokensResponse?.let { id ->
val blockchain = Blockchain.fromNetworkId(id.networkId) ?: return@let null
val derivationPath = id.derivationPath ?: return@let null
val accountIndex = if (blockchain == Blockchain.Chia) {
DerivationIndex.Main
} else {
val recognizer = AccountNodeRecognizer(blockchain = blockchain)
val index = recognizer.recognize(derivationPathValue = derivationPath)?.toInt()
?: return@let null
DerivationIndex(index).getOrNull() ?: return@let null
}
responseCryptoCurrenciesFactory.createCurrency( responseCryptoCurrenciesFactory.createCurrency(
responseToken = id, responseToken = id,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountIndex,
) )
} }
val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency)

View file

@ -72,7 +72,11 @@ internal class SavedSwapTransactionListConverter(
return SwapTransactionListModel( return SwapTransactionListModel(
transactions = value.transactions.map { tx -> transactions = value.transactions.map { tx ->
savedSwapTransactionConverter.convertBack(tx, userWallet, txStatuses) savedSwapTransactionConverter.convertBack(
value = tx,
userWallet = userWallet,
txStatuses = txStatuses,
)
}, },
userWalletId = value.userWalletId, userWalletId = value.userWalletId,
fromCryptoCurrencyId = value.fromCryptoCurrencyId, fromCryptoCurrencyId = value.fromCryptoCurrencyId,

View file

@ -3,11 +3,8 @@
<ManuallySuppressedIssues/> <ManuallySuppressedIssues/>
<CurrentIssues> <CurrentIssues>
<ID>MultilineLambdaItParameter:CustomTokensMerger.kt$CustomTokensMerger${ Timber.e(it, "Unable to fetch token:\n$token") null }</ID> <ID>MultilineLambdaItParameter:CustomTokensMerger.kt$CustomTokensMerger${ Timber.e(it, "Unable to fetch token:\n$token") null }</ID>
<ID>MultilineLambdaItParameter:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository${ it.networkId == blockchainNetworkId &amp;&amp; compareIdWithMigrations(it, coinId) &amp;&amp; it.derivationPath == derivationPath.value }</ID>
<ID>NullableToStringCall:AccountListCryptoCurrenciesFetcher.kt$AccountListCryptoCurrenciesFetcher$${this::class.simpleName}</ID> <ID>NullableToStringCall:AccountListCryptoCurrenciesFetcher.kt$AccountListCryptoCurrenciesFetcher$${this::class.simpleName}</ID>
<ID>NullableToStringCall:DefaultMultiWalletCryptoCurrenciesFetcher.kt$DefaultMultiWalletCryptoCurrenciesFetcher$${this::class.simpleName}</ID> <ID>NullableToStringCall:DefaultMultiWalletCryptoCurrenciesFetcher.kt$DefaultMultiWalletCryptoCurrenciesFetcher$${this::class.simpleName}</ID>
<ID>SuspendFunSwallowedCancellation:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository$runCatching</ID>
<ID>SuspendFunWithFlowReturnType:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository$suspend</ID>
<ID>UseOrEmpty:DefaultYieldSupplyWarningsViewedRepository.kt$DefaultYieldSupplyWarningsViewedRepository$appPreferencesStore.getObjectSet&lt;String&gt;(PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY).firstOrNull() ?: emptySet()</ID> <ID>UseOrEmpty:DefaultYieldSupplyWarningsViewedRepository.kt$DefaultYieldSupplyWarningsViewedRepository$appPreferencesStore.getObjectSet&lt;String&gt;(PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY).firstOrNull() ?: emptySet()</ID>
</CurrentIssues> </CurrentIssues>
</SmellBaseline> </SmellBaseline>

View file

@ -21,6 +21,7 @@ import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
@ -31,6 +32,7 @@ import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
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 kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import timber.log.Timber import timber.log.Timber
@ -301,8 +303,9 @@ internal class DefaultCurrenciesRepository(
) )
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
storedTokens, response = storedTokens,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
@ -357,15 +360,16 @@ internal class DefaultCurrenciesRepository(
val coinId = blockchain.toCoinId() val coinId = blockchain.toCoinId()
val storedCoin = storedTokens.tokens val storedCoin = storedTokens.tokens
.find { .find { token ->
it.networkId == blockchainNetworkId && token.networkId == blockchainNetworkId &&
compareIdWithMigrations(it, coinId) && compareIdWithMigrations(token, coinId) &&
it.derivationPath == derivationPath.value token.derivationPath == derivationPath.value
} ?: error("Coin in this network $networkId not found") } ?: error("Coin in this network $networkId not found")
val coin = responseCryptoCurrenciesFactory.createCurrency( val coin = responseCryptoCurrenciesFactory.createCurrency(
responseToken = storedCoin, responseToken = storedCoin,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
coin as? CryptoCurrency.Coin ?: error("Unable to create currency") coin as? CryptoCurrency.Coin ?: error("Unable to create currency")
@ -525,6 +529,7 @@ internal class DefaultCurrenciesRepository(
} }
} }
@Suppress("SuspendFunWithFlowReturnType")
private suspend fun getCurrenciesForWallet( private suspend fun getCurrenciesForWallet(
userWallet: UserWallet, userWallet: UserWallet,
currencyRawId: CryptoCurrency.RawID, currencyRawId: CryptoCurrency.RawID,
@ -539,6 +544,7 @@ internal class DefaultCurrenciesRepository(
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = storedTokens.copy(tokens = filterResponse), response = storedTokens.copy(tokens = filterResponse),
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
} }
@ -570,7 +576,7 @@ internal class DefaultCurrenciesRepository(
} }
override suspend fun syncTokens(userWalletId: UserWalletId) { override suspend fun syncTokens(userWalletId: UserWalletId) {
runCatching { runSuspendCatching {
val savedCurrencies = requireNotNull( val savedCurrencies = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWalletId), value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
@ -591,6 +597,7 @@ internal class DefaultCurrenciesRepository(
responseCryptoCurrenciesFactory.createCurrencies( responseCryptoCurrenciesFactory.createCurrencies(
response = storedTokens, response = storedTokens,
userWallet = userWallet, userWallet = userWallet,
accountIndex = DerivationIndex.Main,
) )
} }
} }

View file

@ -3,8 +3,6 @@
<ManuallySuppressedIssues/> <ManuallySuppressedIssues/>
<CurrentIssues> <CurrentIssues>
<ID>MultilineLambdaItParameter:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) }</ID> <ID>MultilineLambdaItParameter:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultDerivationsRepository.kt$DefaultDerivationsRepository${ userWallet.update(it.first) it.second }</ID>
<ID>MultilineLambdaItParameter:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ AttemptsPersistentData( attempts = it[PreferencesKeys.getHotWalletUnlockAttemptsKey(id.attemptIdKey())] ?: 0, bootCount = it[PreferencesKeys.getHotWalletUnlockBootKey(id.attemptIdKey())] ?: 0, deadline = it[PreferencesKeys.getHotWalletUnlockDeadlineKey(id.attemptIdKey())] ?: 0L, ) }</ID> <ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ AttemptsPersistentData( attempts = it[PreferencesKeys.getHotWalletUnlockAttemptsKey(id.attemptIdKey())] ?: 0, bootCount = it[PreferencesKeys.getHotWalletUnlockBootKey(id.attemptIdKey())] ?: 0, deadline = it[PreferencesKeys.getHotWalletUnlockDeadlineKey(id.attemptIdKey())] ?: 0L, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(noAuthAttemptId.attemptIdKey())) }</ID> <ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(noAuthAttemptId.attemptIdKey())) }</ID>
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ while (true) { emit(toState(id, it.attempts, it.deadline, it.bootCount)) val remaining = remainingSeconds(it.deadline, it.bootCount) if (remaining &lt;= 0) break delay(timeMillis = 1000) } }</ID> <ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ while (true) { emit(toState(id, it.attempts, it.deadline, it.bootCount)) val remaining = remainingSeconds(it.deadline, it.bootCount) if (remaining &lt;= 0) break delay(timeMillis = 1000) } }</ID>
@ -16,9 +14,7 @@
<ID>SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching</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>UnusedImports:DefaultDerivationsRepository.kt$import com.tangem.common.map</ID>
<ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID> <ID>UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID>
<ID>UseOrEmpty:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap()</ID>
<ID>VarCouldBeVal:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$private var contextualUnlockHotWallet: ConcurrentHashMap&lt;HotWalletId, UnlockHotWallet?&gt; = ConcurrentHashMap()</ID> <ID>VarCouldBeVal:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$private var contextualUnlockHotWallet: ConcurrentHashMap&lt;HotWalletId, UnlockHotWallet?&gt; = ConcurrentHashMap()</ID>
</CurrentIssues> </CurrentIssues>
</SmellBaseline> </SmellBaseline>

View file

@ -2,16 +2,16 @@ package com.tangem.data.wallets.derivations
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.map
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository
import com.tangem.domain.wallets.usecase.BackendId import com.tangem.domain.wallets.usecase.BackendId
import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -29,11 +29,17 @@ internal class DefaultDerivationsRepository @Inject constructor(
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
} }
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.RawID>) { override suspend fun derivePublicKeysByNetworkIds(
userWalletId: UserWalletId,
networkIds: List<Network.RawID>,
accountIndex: DerivationIndex,
) {
val userWallet = userWalletsStore.getSyncStrict(userWalletId) val userWallet = userWalletsStore.getSyncStrict(userWalletId)
when (userWallet) { when (userWallet) {
is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds)
is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) is UserWallet.Hot -> {
hotDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds, accountIndex)
}
}.also { }.also {
userWallet.update(it) userWallet.update(it)
} }
@ -57,9 +63,9 @@ internal class DefaultDerivationsRepository @Inject constructor(
return when (userWallet) { return when (userWallet) {
is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations)
is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations)
}.let { }.let { publicKeysMapByUserWallet ->
userWallet.update(it.first) userWallet.update(publicKeysMapByUserWallet.first)
it.second publicKeysMapByUserWallet.second
} }
} }

View file

@ -8,6 +8,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.wallets.derivations.MissedDerivationsFinder import com.tangem.data.wallets.derivations.MissedDerivationsFinder
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
@ -38,14 +39,16 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor(
override suspend fun derivePublicKeysByNetworkIds( override suspend fun derivePublicKeysByNetworkIds(
userWallet: UserWallet.Hot, userWallet: UserWallet.Hot,
networkIds: List<Network.RawID>, networkIds: List<Network.RawID>,
accountIndex: DerivationIndex,
): UserWallet.Hot { ): UserWallet.Hot {
return derivePublicKeysByNetworks( return derivePublicKeysByNetworks(
userWallet = userWallet, userWallet = userWallet,
networks = networkIds.mapNotNull { networks = networkIds.mapNotNull { networkRawId ->
networkFactory.create( networkFactory.create(
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, blockchain = Blockchain.fromNetworkId(networkRawId.value) ?: return@mapNotNull null,
extraDerivationPath = null, extraDerivationPath = null,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountIndex,
) )
}, },
) )
@ -138,7 +141,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor(
): Map<ByteArrayKey, ExtendedPublicKeysMap> { ): Map<ByteArrayKey, ExtendedPublicKeysMap> {
return (oldKeys.keys + newKeys.keys).toSet() return (oldKeys.keys + newKeys.keys).toSet()
.associateWith { walletKey -> .associateWith { walletKey ->
val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey] ?: emptyMap()) val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey].orEmpty())
val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
ExtendedPublicKeysMap(oldDerivations + newDerivations) ExtendedPublicKeysMap(oldDerivations + newDerivations)

View file

@ -1,7 +1,7 @@
========================================== ==========================================
Detekt Baseline Updater & Issue Counter Detekt Baseline Updater & Issue Counter
========================================== ==========================================
Date: 2025-12-01 16:30:16 Date: 2025-12-02 13:19:49
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: 1453 Total Issues: 1435
Modules with Issues: 62 Modules with Issues: 62
Average Issues per Module: 23 Average Issues per Module: 23
Progress: Progress:
Fixed: 349 out of 1802 (19%) Fixed: 367 out of 1802 (20%)
Remaining: 1453 Remaining: 1435
========================================== ==========================================
All Modules with Issues (sorted by count) All Modules with Issues (sorted by count)
@ -54,14 +54,13 @@ data/visa 22
features/yield-supply/impl 21 features/yield-supply/impl 21
features/tangempay/details/impl 21 features/tangempay/details/impl 21
data/nft 20 data/nft 20
data/wallets 18
features/swap/data 15 features/swap/data 15
data/wallets 14
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 domain/account/status 11
data/onramp 11 data/onramp 11
data/manage-tokens 11
core/datasource 11 core/datasource 11
data/markets 10 data/markets 10
features/details/impl 9 features/details/impl 9
@ -72,22 +71,23 @@ features/referral/impl 8
domain/transaction 8 domain/transaction 8
libs/tangem-sdk-api 7 libs/tangem-sdk-api 7
data/txhistory 7 data/txhistory 7
data/tokens 7
data/account 7
features/welcome/impl 6 features/welcome/impl 6
domain/markets 6
data/wallet-manager 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
domain/markets 5
domain/legacy 5 domain/legacy 5
data/transaction 5 data/transaction 5
data/account 5
features/referral/domain 4 features/referral/domain 4
features/biometry/impl 4 features/biometry/impl 4
data/tokens 4
features/txhistory/impl 3 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/manage-tokens 3
common/routing 3 common/routing 3
features/account/api 2 features/account/api 2
data/promo 2 data/promo 2

View file

@ -5,7 +5,6 @@
<ID>BooleanPropertyNaming:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase$private val useNewRepository: Boolean</ID> <ID>BooleanPropertyNaming:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase$private val useNewRepository: Boolean</ID>
<ID>BooleanPropertyNaming:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$val showStakingNotification = if (!hideClicked &amp;&amp; walletFirstUsageDate != 0L) { currentDate - walletFirstUsageDate &gt; TWO_WEEKS_IN_MILLIS } else { false }</ID> <ID>BooleanPropertyNaming:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$val showStakingNotification = if (!hideClicked &amp;&amp; walletFirstUsageDate != 0L) { currentDate - walletFirstUsageDate &gt; TWO_WEEKS_IN_MILLIS } else { false }</ID>
<ID>MultilineLambdaItParameter:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase${ val blockchain = Blockchain.fromNetworkId(it.networkId) supportedBlockchains.contains(blockchain) }</ID> <ID>MultilineLambdaItParameter:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase${ val blockchain = Blockchain.fromNetworkId(it.networkId) supportedBlockchains.contains(blockchain) }</ID>
<ID>MultilineLambdaItParameter:SaveMarketTokensUseCase.kt$SaveMarketTokensUseCase${ marketsTokenRepository.createCryptoCurrency( userWalletId = userWalletId, token = tokenMarketParams, network = it, ) }</ID>
<ID>SuspendFunWithFlowReturnType:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$suspend</ID> <ID>SuspendFunWithFlowReturnType:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$suspend</ID>
<ID>SuspendFunWithFlowReturnType:MarketsTokenRepository.kt$MarketsTokenRepository$suspend</ID> <ID>SuspendFunWithFlowReturnType:MarketsTokenRepository.kt$MarketsTokenRepository$suspend</ID>
</CurrentIssues> </CurrentIssues>

View file

@ -2,6 +2,7 @@ package com.tangem.domain.markets
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -45,11 +46,11 @@ class SaveMarketTokensUseCase(
removedNetworks: Set<TokenMarketInfo.Network>, removedNetworks: Set<TokenMarketInfo.Network>,
): Either<Throwable, Unit> = Either.catch { ): Either<Throwable, Unit> = Either.catch {
if (removedNetworks.isNotEmpty()) { if (removedNetworks.isNotEmpty()) {
val removedCurrencies = removedNetworks.mapNotNull { val removedCurrencies = removedNetworks.mapNotNull { network ->
marketsTokenRepository.createCryptoCurrency( marketsTokenRepository.createCryptoCurrency(
userWalletId = userWalletId, userWalletId = userWalletId,
token = tokenMarketParams, token = tokenMarketParams,
network = it, network = network,
) )
} }
@ -60,13 +61,15 @@ class SaveMarketTokensUseCase(
derivationsRepository.derivePublicKeysByNetworkIds( derivationsRepository.derivePublicKeysByNetworkIds(
userWalletId = userWalletId, userWalletId = userWalletId,
networkIds = addedNetworks.map { Network.RawID(it.networkId) }, networkIds = addedNetworks.map { Network.RawID(it.networkId) },
accountIndex = DerivationIndex.Main,
) )
val addedCurrencies = addedNetworks.mapNotNull { val addedCurrencies = addedNetworks.mapNotNull { network ->
marketsTokenRepository.createCryptoCurrency( marketsTokenRepository.createCryptoCurrency(
userWalletId = userWalletId, userWalletId = userWalletId,
token = tokenMarketParams, token = tokenMarketParams,
network = it, network = network,
accountIndex = DerivationIndex.Main,
) )
} }

View file

@ -2,6 +2,7 @@ package com.tangem.domain.wallets.derivations
import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -13,7 +14,11 @@ interface DerivationsRepository {
@Throws @Throws
suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.RawID>) suspend fun derivePublicKeysByNetworkIds(
userWalletId: UserWalletId,
networkIds: List<Network.RawID>,
accountIndex: DerivationIndex,
)
@Throws @Throws
suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>) suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>)

View file

@ -2,6 +2,7 @@ package com.tangem.domain.wallets.derivations
import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
@ -16,6 +17,7 @@ interface HotMapDerivationsRepository {
suspend fun derivePublicKeysByNetworkIds( suspend fun derivePublicKeysByNetworkIds(
userWallet: UserWallet.Hot, userWallet: UserWallet.Hot,
networkIds: List<Network.RawID>, networkIds: List<Network.RawID>,
accountIndex: DerivationIndex,
): UserWallet.Hot ): UserWallet.Hot
@Throws @Throws

View file

@ -37,7 +37,6 @@ dependencies {
implementation(projects.domain.tokens.models) implementation(projects.domain.tokens.models)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.walletManager) implementation(projects.domain.walletManager)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.models) implementation(projects.domain.models)
implementation(projects.domain.wallets) implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models) implementation(projects.domain.wallets.models)
@ -45,6 +44,9 @@ dependencies {
implementation(projects.domain.express.models) implementation(projects.domain.express.models)
implementation(projects.domain.account.status) implementation(projects.domain.account.status)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
/** Data */ /** Data */
implementation(projects.data.common) implementation(projects.data.common)

View file

@ -17,6 +17,7 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModelInner import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModelInner
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
internal class SavedSwapTransactionListConverter( internal class SavedSwapTransactionListConverter(
@ -75,9 +76,23 @@ internal class SavedSwapTransactionListConverter(
.map { tx -> .map { tx ->
val status = txStatuses[tx.txId] val status = txStatuses[tx.txId]
val refundCurrency = status?.refundTokensResponse?.let { id -> val refundCurrency = status?.refundTokensResponse?.let { id ->
val blockchain = Blockchain.fromNetworkId(id.networkId) ?: return@let null
val derivationPath = id.derivationPath ?: return@let null
val accountIndex = if (blockchain == Blockchain.Chia) {
DerivationIndex.Main
} else {
val recognizer = AccountNodeRecognizer(blockchain = blockchain)
val index = recognizer.recognize(derivationPathValue = derivationPath)?.toInt()
?: return@let null
DerivationIndex(index).getOrNull() ?: return@let null
}
responseCryptoCurrenciesFactory.createCurrency( responseCryptoCurrenciesFactory.createCurrency(
responseToken = id, responseToken = id,
userWallet = userWallet, userWallet = userWallet,
accountIndex = accountIndex,
) )
} }
val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency)