From 93dd76610b74d02bcfaa1e64dd8753b78845925e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Oct 2024 15:46:12 +0400 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../DefaultManageTokensRepository.kt | 143 ++++++++++++------ .../managetokens/di/ManageTokensDataModule.kt | 3 + .../utils/ManagedCryptoCurrencyFactory.kt | 62 ++++++-- 3 files changed, 148 insertions(+), 60 deletions(-) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 49b58cb102..955401f019 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -1,8 +1,8 @@ package com.tangem.data.managetokens import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.data.common.currency.getBlockchain @@ -15,7 +15,9 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.testnet.TestnetTokensStorage import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.extensions.canHandleBlockchain import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.supportedBlockchains @@ -31,6 +33,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.pagination.BatchFetchResult import com.tangem.pagination.BatchListSource import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher +import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request import com.tangem.pagination.toBatchFlow import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -40,6 +43,7 @@ internal class DefaultManageTokensRepository( private val userWalletsStore: UserWalletsStore, private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher, private val appPreferencesStore: AppPreferencesStore, + private val testnetTokensStorage: TestnetTokensStorage, private val dispatchers: CoroutineDispatcherProvider, ) : ManageTokensRepository { @@ -59,7 +63,6 @@ internal class DefaultManageTokensRepository( ).toBatchFlow() } - @Suppress("ComplexCondition") private fun createFetcher( batchSize: Int, ): LimitOffsetBatchFetcher> = LimitOffsetBatchFetcher( @@ -67,59 +70,101 @@ internal class DefaultManageTokensRepository( batchSize = batchSize, subFetcher = { request, _, isFirstBatchFetching -> val userWallet = request.params.userWalletId?.let { getUserWallet(it) } - val supportedBlockchains = getSupportedBlockchains(userWallet) - val searchText = request.params.searchText?.takeIf { it.isNotBlank() } - val call = suspend { - tangemTechApi.getCoins( - networkIds = supportedBlockchains.joinToString( - separator = ",", - transform = Blockchain::toNetworkId, - ), - active = true, - searchText = searchText, - offset = request.offset * request.limit, - limit = request.limit, - ).getOrThrow() - } - - val coinsResponse = if (isFirstBatchFetching) { - call() + if (userWallet?.scanResponse?.card?.isTestCard == true) { + fetchTestnetCurrencies(userWallet, request) } else { - retryOnError(call = call) + fetchCurrencies(userWallet, request, isFirstBatchFetching) } - // filter l2 coins - val updatedCoinsResponse = coinsResponse.copy( - coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) }, - ) - - val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) } - val items = if (isFirstBatchFetching && - tokensResponse != null && - userWallet != null && - request.params.searchText.isNullOrBlank() - ) { - managedCryptoCurrencyFactory.createWithCustomTokens( - coinsResponse = updatedCoinsResponse, - tokensResponse = tokensResponse, - derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, - ) - } else { - managedCryptoCurrencyFactory.create( - coinsResponse = updatedCoinsResponse, - tokensResponse = tokensResponse, - derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider, - ) - } - - BatchFetchResult.Success( - data = items, - empty = items.isEmpty(), - last = items.size < request.limit, - ) }, ) + @Suppress("ComplexCondition") + private suspend fun fetchCurrencies( + userWallet: UserWallet?, + request: Request, + isFirstBatchFetching: Boolean, + ): BatchFetchResult.Success> { + val supportedBlockchains = getSupportedBlockchains(userWallet) + + val call = suspend { + tangemTechApi.getCoins( + networkIds = supportedBlockchains.joinToString( + separator = ",", + transform = Blockchain::toNetworkId, + ), + active = true, + searchText = request.params.searchText, + offset = request.offset * request.limit, + limit = request.limit, + ).getOrThrow() + } + + val coinsResponse = if (isFirstBatchFetching) { + call() + } else { + retryOnError(call = call) + } + // filter l2 coins + val updatedCoinsResponse = coinsResponse.copy( + coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) }, + ) + + val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) } + val items = if (isFirstBatchFetching && + tokensResponse != null && + userWallet != null && + request.params.searchText.isNullOrBlank() + ) { + managedCryptoCurrencyFactory.createWithCustomTokens( + coinsResponse = updatedCoinsResponse, + tokensResponse = tokensResponse, + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) + } else { + managedCryptoCurrencyFactory.create( + coinsResponse = updatedCoinsResponse, + tokensResponse = tokensResponse, + derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider, + ) + } + + return BatchFetchResult.Success( + data = items, + empty = items.isEmpty(), + last = items.size < request.limit, + ) + } + + private suspend fun fetchTestnetCurrencies( + userWallet: UserWallet, + request: Request, + ): BatchFetchResult.Success> { + val searchText = request.params.searchText + val testnetTokensConfig = testnetTokensStorage.getConfig() + + val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens( + testnetTokensConfig = if (!searchText.isNullOrBlank()) { + testnetTokensConfig.copy( + tokens = testnetTokensConfig.tokens.filter { token -> + token.symbol.contains(other = searchText, ignoreCase = true) || + token.name.contains(other = searchText, ignoreCase = true) + }, + ) + } else { + testnetTokensConfig + }, + tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId), + derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider, + ) + + return BatchFetchResult.Success( + data = items, + empty = items.isEmpty(), + last = true, + ) + } + private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet { return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find a user wallet with provided ID: $userWalletId" diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index d5d9009ec3..5433dc0701 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -5,6 +5,7 @@ import com.tangem.data.managetokens.DefaultManageTokensRepository import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.testnet.TestnetTokensStorage import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository @@ -27,6 +28,7 @@ internal object ManageTokensDataModule { userWalletsStore: UserWalletsStore, manageTokensUpdateFetcher: ManageTokensUpdateFetcher, appPreferencesStore: AppPreferencesStore, + testnetTokensStorage: TestnetTokensStorage, dispatchers: CoroutineDispatcherProvider, ): ManageTokensRepository { return DefaultManageTokensRepository( @@ -34,6 +36,7 @@ internal object ManageTokensDataModule { userWalletsStore, manageTokensUpdateFetcher, appPreferencesStore, + testnetTokensStorage, dispatchers, ) } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt index 5a5b9fd6eb..ffdbb9e58d 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt @@ -12,6 +12,7 @@ import com.tangem.data.common.currency.getNetwork import com.tangem.data.common.currency.getTokenId import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.testnet.models.TestnetTokensConfig import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork @@ -34,16 +35,49 @@ internal class ManagedCryptoCurrencyFactory { tokensResponse: UserTokensResponse, derivationStyleProvider: DerivationStyleProvider, ): List { - val customTokens = tokensResponse.tokens - .mapNotNull { token -> - maybeCreateCustomToken(token, coinsResponse.imageHost, derivationStyleProvider) - } - + val customTokens = createCustomTokens(tokensResponse, derivationStyleProvider) val tokens = create(coinsResponse, tokensResponse, derivationStyleProvider) return customTokens + tokens } + fun createTestnetWithCustomTokens( + testnetTokensConfig: TestnetTokensConfig, + tokensResponse: UserTokensResponse?, + derivationStyleProvider: DerivationStyleProvider, + ): List { + val customTokens = tokensResponse + ?.let { createCustomTokens(it, derivationStyleProvider) } + ?: emptyList() + val testnetTokens = testnetTokensConfig.tokens.map { testnetToken -> + ManagedCryptoCurrency.Token( + id = ManagedCryptoCurrency.ID(testnetToken.id), + name = testnetToken.name, + symbol = testnetToken.symbol, + iconUrl = getIconUrl(testnetToken.id), + availableNetworks = testnetToken.networks?.mapNotNull { network -> + createSource( + networkId = network.id, + contractAddress = network.address, + decimals = network.decimalCount ?: return@mapNotNull null, + derivationStyleProvider = derivationStyleProvider, + ) + } ?: emptyList(), + addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, derivationStyleProvider), + ) + } + + return customTokens + testnetTokens + } + + private fun createCustomTokens( + tokensResponse: UserTokensResponse, + derivationStyleProvider: DerivationStyleProvider, + ): List = tokensResponse.tokens + .mapNotNull { token -> + maybeCreateCustomToken(token, null, derivationStyleProvider) + } + private fun maybeCreateCustomToken( token: UserTokensResponse.Token, imageHost: String?, @@ -100,23 +134,29 @@ internal class ManagedCryptoCurrencyFactory { symbol = coinResponse.symbol, iconUrl = getIconUrl(coinResponse.id, imageHost), availableNetworks = updatedNetworks.mapNotNull { network -> - createSource(network, derivationStyleProvider) + createSource( + networkId = network.networkId, + contractAddress = network.contractAddress, + decimals = network.decimalCount?.toInt() ?: return@mapNotNull null, + derivationStyleProvider = derivationStyleProvider, + ) }, addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, derivationStyleProvider), ) } private fun createSource( - networkResponse: CoinsResponse.Coin.Network, + networkId: String, + contractAddress: String?, + decimals: Int, derivationStyleProvider: DerivationStyleProvider?, extraDerivationPath: String? = null, ): SourceNetwork? { - val blockchain = Blockchain.fromNetworkId(networkResponse.networkId) + val blockchain = Blockchain.fromNetworkId(networkId) ?.takeIf { it.isSupportedInApp() } ?: return null val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null - val contractAddress = networkResponse.contractAddress return if (contractAddress.isNullOrBlank()) { SourceNetwork.Main( @@ -127,7 +167,7 @@ internal class ManagedCryptoCurrencyFactory { } else { SourceNetwork.Default( network = network, - decimals = requireNotNull(networkResponse.decimalCount?.toInt()), + decimals = decimals, contractAddress = contractAddress, ) } @@ -157,7 +197,7 @@ internal class ManagedCryptoCurrencyFactory { } } - private fun getIconUrl(id: String, imageHost: String?): String { + private fun getIconUrl(id: String, imageHost: String? = DEFAULT_IMAGE_HOST): String { return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png" } From 8e97517459f2488bc3c4043a7f397b665e76bce7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Oct 2024 15:34:04 +0400 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../tangem/core/ui/extensions/BlockchainIcons.kt | 5 +++++ core/ui/src/main/res/drawable/ic_sui_22.xml | 9 +++++++++ core/ui/src/main/res/drawable/img_sui_22.xml | 15 +++++++++++++++ gradle/dependencies.toml | 2 +- .../com/tangem/blockchainsdk/utils/Blockchain.kt | 5 +++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 core/ui/src/main/res/drawable/ic_sui_22.xml create mode 100644 core/ui/src/main/res/drawable/img_sui_22.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 59fe1fa692..d014d038e0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -75,6 +75,7 @@ fun getActiveIconRes(blockchainId: String): Int { "cyber", "cyber/test" -> R.drawable.img_cyber_22 "sei", "sei/test" -> R.drawable.img_sei_22 "internet-computer" -> R.drawable.img_icp_22 + "sui", "sui/test" -> R.drawable.img_sui_22 else -> R.drawable.ic_alert_24 } } @@ -151,6 +152,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "cyber", "cyber/test" -> R.drawable.img_cyber_22 "sei", "sei/test" -> R.drawable.img_sei_22 "internet-computer" -> R.drawable.img_icp_22 + "sui", "sui/test" -> R.drawable.img_sui_22 else -> R.drawable.ic_alert_24 } } @@ -224,6 +226,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "cyber", "cyber/test" -> R.drawable.img_cyber_22 "sei", "sei/test" -> R.drawable.img_sei_22 "internet-computer" -> R.drawable.img_icp_22 + "sui", "sui/test" -> R.drawable.img_sui_22 else -> R.drawable.ic_alert_24 } } @@ -300,6 +303,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "cyber", "cyber/test" -> R.drawable.ic_cyber_22 "sei", "sei/test" -> R.drawable.ic_sei_22 "internet-computer" -> R.drawable.ic_icp_22 + "sui", "sui/test" -> R.drawable.ic_sui_22 else -> R.drawable.ic_alert_24 } } @@ -376,6 +380,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "cyber", "cyber/test" -> R.drawable.ic_cyber_22 "sei", "sei/test" -> R.drawable.ic_sei_22 "internet-computer" -> R.drawable.ic_icp_22 + "sui", "sui/test" -> R.drawable.ic_sui_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_sui_22.xml b/core/ui/src/main/res/drawable/ic_sui_22.xml new file mode 100644 index 0000000000..c6595dd4d7 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_sui_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_sui_22.xml b/core/ui/src/main/res/drawable/img_sui_22.xml new file mode 100644 index 0000000000..750a7815c8 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_sui_22.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e71e76d7c8..3ebdc9787b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -89,7 +89,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.16-803" +tangemBlockchainSdk = "release-app_5.16-804" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.16-386" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 6c71737687..ed07736123 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -126,6 +126,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "sei-network" -> Blockchain.Sei "sei-network/test" -> Blockchain.SeiTestnet "internet-computer" -> Blockchain.InternetComputer + "sui" -> Blockchain.Sui + "sui/test" -> Blockchain.SuiTestnet else -> null } } @@ -253,6 +255,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.Sei -> "sei-network" Blockchain.SeiTestnet -> "sei-network/test" Blockchain.InternetComputer -> "internet-computer" + Blockchain.Sui -> "sui" + Blockchain.SuiTestnet -> "sui/test" } } @@ -337,6 +341,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum" Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network" Blockchain.InternetComputer -> "internet-computer" + Blockchain.Sui, Blockchain.SuiTestnet -> "sui" } }