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/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"
}
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"
}
}