Updated on 2026-08-14
This commit is contained in:
parent
473a936863
commit
df938519a2
13 changed files with 155 additions and 32 deletions
|
|
@ -48,6 +48,14 @@ internal object ManageTokensDomainModule {
|
|||
return CheckIsCurrencyNotAddedUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRemoveCustomManagedCryptoCurrencyUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): RemoveCustomManagedCryptoCurrencyUseCase {
|
||||
return RemoveCustomManagedCryptoCurrencyUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSaveManagedTokensUseCase(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.tangem.common.extensions.calculateHashCode
|
||||
|
||||
data class UserTokensResponse(
|
||||
@Json(name = "version") val version: Int = 0,
|
||||
|
|
@ -17,7 +18,23 @@ data class UserTokensResponse(
|
|||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
@Json(name = "contractAddress") val contractAddress: String?,
|
||||
)
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val otherToken = other as? Token ?: return false
|
||||
|
||||
return otherToken.contractAddress == this.contractAddress &&
|
||||
otherToken.networkId == this.networkId &&
|
||||
otherToken.derivationPath == this.derivationPath &&
|
||||
otherToken.decimals == this.decimals
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = calculateHashCode(
|
||||
contractAddress.hashCode(),
|
||||
networkId.hashCode(),
|
||||
derivationPath.hashCode(),
|
||||
decimals.hashCode(),
|
||||
)
|
||||
}
|
||||
|
||||
enum class GroupType {
|
||||
@Json(name = "none")
|
||||
|
|
|
|||
|
|
@ -1,37 +1,46 @@
|
|||
package com.tangem.data.managetokens
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoTokenAddressConverter
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTokenAddressConverter
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getBlockchain
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.data.managetokens.utils.TokenAddressesConverter
|
||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
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.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.managetokens.model.AddCustomTokenForm
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultCustomTokensRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : CustomTokensRepository {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val tokenAddressConverter = TokenAddressesConverter()
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
|
||||
override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
|
|
@ -73,7 +82,7 @@ internal class DefaultCustomTokensRepository(
|
|||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId)
|
||||
?: error("User wallet not found")
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
val tokenAddress = convertTokenAddress(
|
||||
val tokenAddress = tokenAddressConverter.convertTokenAddress(
|
||||
networkId,
|
||||
contractAddress,
|
||||
symbol = null,
|
||||
|
|
@ -112,10 +121,7 @@ internal class DefaultCustomTokensRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun createCoin(
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Coin {
|
||||
override fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin {
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
|
||||
return cryptoCurrencyFactory.createCoin(network)
|
||||
|
|
@ -127,7 +133,7 @@ internal class DefaultCustomTokensRepository(
|
|||
formValues: AddCustomTokenForm.Validated.All,
|
||||
): CryptoCurrency.Token {
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
val tokenAddress = convertTokenAddress(
|
||||
val tokenAddress = tokenAddressConverter.convertTokenAddress(
|
||||
networkId,
|
||||
formValues.contractAddress,
|
||||
formValues.symbol,
|
||||
|
|
@ -143,20 +149,54 @@ internal class DefaultCustomTokensRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private fun convertTokenAddress(networkId: Network.ID, contractAddress: String, symbol: String?): String {
|
||||
val convertedAddress = when (getBlockchain(networkId)) {
|
||||
Blockchain.Hedera,
|
||||
Blockchain.HederaTestnet,
|
||||
-> HederaTokenAddressConverter().convertToTokenId(contractAddress)
|
||||
Blockchain.Cardano -> {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
CardanoTokenAddressConverter().convertToFingerprint(contractAddress, symbol)
|
||||
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) =
|
||||
withContext(dispatchers.io) {
|
||||
val cryptoCurrency = when (currency) {
|
||||
is ManagedCryptoCurrency.Custom.Coin -> createCoin(currency.network.id, currency.network.derivationPath)
|
||||
is ManagedCryptoCurrency.Custom.Token -> cryptoCurrencyFactory.createToken(
|
||||
network = currency.network,
|
||||
rawId = currency.currencyId.rawCurrencyId,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
decimals = currency.decimals,
|
||||
contractAddress = currency.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform remove currency action" },
|
||||
)
|
||||
val token = userTokensResponseFactory.createResponseToken(cryptoCurrency)
|
||||
storeAndPushTokens(
|
||||
userWalletId = userWalletId,
|
||||
response = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }),
|
||||
)
|
||||
when (cryptoCurrency) {
|
||||
is CryptoCurrency.Coin -> walletManagersFacade.remove(userWalletId, setOf(cryptoCurrency.network))
|
||||
is CryptoCurrency.Token -> walletManagersFacade.removeTokens(userWalletId, setOf(cryptoCurrency))
|
||||
}
|
||||
else -> contractAddress
|
||||
}
|
||||
|
||||
return requireNotNull(convertedAddress) {
|
||||
"Token contract address is invalid"
|
||||
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
|
||||
pushTokens(userWalletId, response)
|
||||
}
|
||||
|
||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
||||
Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(key.stringValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,12 +52,14 @@ internal object ManageTokensDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): CustomTokensRepository {
|
||||
return DefaultCustomTokensRepository(
|
||||
tangemTechApi,
|
||||
userWalletsStore,
|
||||
appPreferencesStore,
|
||||
walletManagersFacade,
|
||||
dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
iconUrl = token.id?.let { getIconUrl(it, imageHost) },
|
||||
contractAddress = contractAddress,
|
||||
network = network,
|
||||
decimals = token.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.data.managetokens.utils
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoTokenAddressConverter
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTokenAddressConverter
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.data.common.currency.getBlockchain
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
internal class TokenAddressesConverter {
|
||||
private val hederaTokenAddressConverter = HederaTokenAddressConverter()
|
||||
private val cardanoTokenAddressConverter = CardanoTokenAddressConverter()
|
||||
|
||||
fun convertTokenAddress(networkId: Network.ID, contractAddress: String, symbol: String?): String {
|
||||
val convertedAddress = when (getBlockchain(networkId)) {
|
||||
Blockchain.Hedera,
|
||||
Blockchain.HederaTestnet,
|
||||
-> hederaTokenAddressConverter.convertToTokenId(contractAddress)
|
||||
Blockchain.Cardano -> {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
cardanoTokenAddressConverter.convertToFingerprint(contractAddress, symbol)
|
||||
}
|
||||
else -> contractAddress
|
||||
}
|
||||
|
||||
return requireNotNull(convertedAddress) {
|
||||
"Token contract address is invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -153,15 +153,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val token = userTokensResponseFactory.createResponseToken(currency)
|
||||
storeAndPushTokens(
|
||||
userWalletId = userWalletId,
|
||||
response = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens.filterNot {
|
||||
// it's better to compare by fields, to support renaming and etc
|
||||
it.contractAddress == token.contractAddress &&
|
||||
it.networkId == token.networkId &&
|
||||
it.derivationPath == token.derivationPath &&
|
||||
it.decimals == token.decimals
|
||||
},
|
||||
),
|
||||
response = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ sealed class ManagedCryptoCurrency {
|
|||
override val iconUrl: String?,
|
||||
override val network: Network,
|
||||
val contractAddress: String,
|
||||
val decimals: Int,
|
||||
) : Custom()
|
||||
|
||||
data class Coin(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.managetokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class RemoveCustomManagedCryptoCurrencyUseCase(private val repository: CustomTokensRepository) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
customCurrency: ManagedCryptoCurrency.Custom,
|
||||
): Either<Throwable, Unit> {
|
||||
return Either.catch {
|
||||
repository.removeCurrency(userWalletId, customCurrency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.managetokens.repository
|
||||
|
||||
import com.tangem.domain.managetokens.model.AddCustomTokenForm
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -23,11 +24,13 @@ interface CustomTokensRepository {
|
|||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Token?
|
||||
|
||||
suspend fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin
|
||||
fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin
|
||||
|
||||
suspend fun createCustomToken(
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
formValues: AddCustomTokenForm.Validated.All,
|
||||
): CryptoCurrency.Token
|
||||
|
||||
suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.GetManagedTokensUseCase
|
||||
import com.tangem.domain.managetokens.RemoveCustomManagedCryptoCurrencyUseCase
|
||||
import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext
|
||||
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
||||
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
||||
|
|
@ -29,6 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ import javax.inject.Inject
|
|||
internal class ManageTokensListManager @Inject constructor(
|
||||
private val getManagedTokensUseCase: GetManagedTokensUseCase,
|
||||
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
|
||||
private val removeCustomCurrencyUseCase: RemoveCustomManagedCryptoCurrencyUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ManageTokensUiActions {
|
||||
|
|
@ -160,6 +163,12 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = false)
|
||||
}
|
||||
|
||||
override fun removeCustomCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) {
|
||||
scope.launch {
|
||||
removeCustomCurrencyUseCase.invoke(userWalletId, currency)
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkNeedToShowRemoveNetworkWarning(
|
||||
currency: ManagedCryptoCurrency.Token,
|
||||
network: Network,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ internal interface ManageTokensUiActions {
|
|||
|
||||
fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)
|
||||
|
||||
fun removeCustomCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom)
|
||||
|
||||
fun checkNeedToShowRemoveNetworkWarning(currency: ManagedCryptoCurrency.Token, network: Network): Boolean
|
||||
|
||||
suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ internal class ManageTokensUiManager(
|
|||
network = currency.network,
|
||||
isCoin = currency is ManagedCryptoCurrency.Custom.Coin,
|
||||
onConfirm = {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
val userWalletId = requireNotNull(state.value.userWalletId) { "UserWalletId is null. Can not remove" }
|
||||
actions.removeCustomCurrency(userWalletId = userWalletId, currency = currency)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue