Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-29 18:44:22 +05:00
parent 473a936863
commit df938519a2
13 changed files with 155 additions and 32 deletions

View file

@ -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),
)
}
}

View file

@ -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,
)
}

View file

@ -77,6 +77,7 @@ internal class ManagedCryptoCurrencyFactory {
iconUrl = token.id?.let { getIconUrl(it, imageHost) },
contractAddress = contractAddress,
network = network,
decimals = token.decimals,
)
}
}

View file

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

View file

@ -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 }),
)
}