diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt index 9af9e67943..38d8376ff2 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt @@ -12,6 +12,29 @@ import com.tangem.blockchain.common.Token as SdkToken // FIXME: Make internal class CryptoCurrencyFactory { + @Suppress("LongParameterList") // Yep, it's long + fun createToken( + network: Network, + rawId: String?, + name: String, + symbol: String, + decimals: Int, + contractAddress: String, + ): CryptoCurrency.Token { + val id = getTokenId(network, rawId, contractAddress) + + return CryptoCurrency.Token( + id = id, + network = network, + name = name, + symbol = symbol, + decimals = decimals, + iconUrl = rawId?.let(::getTokenIconUrlFromDefaultHost), + isCustom = isCustomToken(id, network), + contractAddress = contractAddress, + ) + } + fun createToken( sdkToken: SdkToken, blockchain: Blockchain, @@ -49,15 +72,7 @@ class CryptoCurrencyFactory { } val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null - return CryptoCurrency.Coin( - id = getCoinId(network, blockchain.toCoinId()), - network = network, - name = blockchain.fullName, - symbol = blockchain.currency, - iconUrl = getCoinIconUrl(blockchain), - decimals = blockchain.decimals(), - isCustom = isCustomCoin(network), - ) + return createCoin(network) } fun createCoin( @@ -69,6 +84,20 @@ class CryptoCurrencyFactory { return createCoin(blockchain, extraDerivationPath, derivationStyleProvider) } + fun createCoin(network: Network): CryptoCurrency.Coin { + val blockchain = Blockchain.fromId(network.id.value) + + return CryptoCurrency.Coin( + id = getCoinId(network, blockchain.toCoinId()), + network = network, + name = blockchain.fullName, + symbol = blockchain.currency, + iconUrl = getCoinIconUrl(blockchain), + decimals = blockchain.decimals(), + isCustom = isCustomCoin(network), + ) + } + fun createToken( token: Token, networkId: String, diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt index 8f2e5b60ac..6febfc5fb7 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt @@ -11,6 +11,21 @@ fun getBlockchain(networkId: Network.ID): Blockchain { return Blockchain.fromId(networkId.value) } +fun getNetwork(networkId: Network.ID, derivationPath: Network.DerivationPath): Network { + val blockchain = getBlockchain(networkId) + + return Network( + id = networkId, + backendId = blockchain.toNetworkId(), + name = blockchain.getNetworkName(), + isTestnet = blockchain.isTestnet(), + derivationPath = derivationPath, + currencySymbol = blockchain.currency, + standardType = getNetworkStandardType(blockchain), + hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource, + ) +} + fun getNetwork( blockchain: Blockchain, extraDerivationPath: String?, diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt index 72018fc4ed..2c2c8d95ad 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/TokensOperations.kt @@ -85,7 +85,7 @@ private fun getCurrencyIdBody(network: Network): CurrencyIdBody { } } -private fun getTokenIconUrlFromDefaultHost(tokenId: String): String { +fun getTokenIconUrlFromDefaultHost(tokenId: String): String { return buildString { append(DEFAULT_TOKENS_ICONS_HOST) append('/') diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt index 50f20df32c..8488ff5b54 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -1,16 +1,46 @@ package com.tangem.data.managetokens +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.data.common.currency.CryptoCurrencyFactory +import com.tangem.data.common.currency.getNetwork +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.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.repository.CustomTokensRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext -internal class DefaultCustomTokensRepository : CustomTokensRepository { +internal class DefaultCustomTokensRepository( + private val tangemTechApi: TangemTechApi, + private val userWalletsStore: UserWalletsStore, + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : CustomTokensRepository { - override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean { - TODO("Should be implemented in [REDACTED_JIRA]") - } + private val cryptoCurrencyFactory = CryptoCurrencyFactory() + + override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean = + withContext(dispatchers.io) { + when (val blockchain = Blockchain.fromId(networkId.value)) { + Blockchain.Unknown, + Blockchain.Binance, + Blockchain.BinanceTestnet, + -> true + Blockchain.Cardano -> blockchain.validateContractAddress(contractAddress.lowercase()) + else -> blockchain.validateAddress(contractAddress.lowercase()) + } + } override suspend fun isCurrencyNotAdded( userWalletId: UserWalletId, @@ -18,7 +48,17 @@ internal class DefaultCustomTokensRepository : CustomTokensRepository { derivationPath: Network.DerivationPath, contractAddress: String?, ): Boolean { - TODO("Should be implemented in [REDACTED_JIRA]") + return withContext(dispatchers.io) { + val storedCurrencies: UserTokensResponse = appPreferencesStore.getObjectSyncOrNull( + key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue), + ) ?: error("User tokens not found") + + storedCurrencies.tokens.none { token -> + Blockchain.fromId(networkId.value).toNetworkId() == token.networkId && + derivationPath.value == token.derivationPath && + contractAddress.equals(token.contractAddress, ignoreCase = true) + } + } } override suspend fun findToken( @@ -26,15 +66,51 @@ internal class DefaultCustomTokensRepository : CustomTokensRepository { contractAddress: String, networkId: Network.ID, derivationPath: Network.DerivationPath, - ): CryptoCurrency.Token? { - TODO("Should be implemented in [REDACTED_JIRA]") + ): CryptoCurrency.Token? = withContext(dispatchers.io) { + val userWallet = userWalletsStore.getSyncOrNull(userWalletId) + ?: error("User wallet not found") + val network = getNetwork(networkId, derivationPath) + + val supportedTokenNetworkIds = userWallet.scanResponse.card + .supportedBlockchains(userWallet.scanResponse.cardTypesResolver) + .filter(Blockchain::canHandleTokens) + .map(Blockchain::toNetworkId) + + val response = tangemTechApi.getCoins( + contractAddress = contractAddress, + networkIds = network.backendId, + active = true, + ).getOrThrow() + + response.coins.firstNotNullOfOrNull { coin -> + val coinNetwork = coin.networks.firstOrNull { network -> + (network.contractAddress != null || network.decimalCount != null) && + network.contractAddress.equals(contractAddress, ignoreCase = true) && + network.networkId in supportedTokenNetworkIds + } + + if (coinNetwork != null) { + cryptoCurrencyFactory.createToken( + network = network, + rawId = coin.id, + name = coin.name, + symbol = coin.symbol, + decimals = coinNetwork.decimalCount!!.toInt(), + contractAddress = contractAddress, + ) + } else { + null + } + } } override suspend fun createCoin( networkId: Network.ID, derivationPath: Network.DerivationPath, ): CryptoCurrency.Coin { - TODO("Should be implemented in [REDACTED_JIRA]") + val network = getNetwork(networkId, derivationPath) + + return cryptoCurrencyFactory.createCoin(network) } override suspend fun createCustomToken( @@ -42,6 +118,15 @@ internal class DefaultCustomTokensRepository : CustomTokensRepository { derivationPath: Network.DerivationPath, formValues: AddCustomTokenForm.Validated.All, ): CryptoCurrency.Token { - TODO("Should be implemented in [REDACTED_JIRA]") + val network = getNetwork(networkId, derivationPath) + + return cryptoCurrencyFactory.createToken( + network = network, + rawId = null, + name = formValues.name, + symbol = formValues.symbol, + decimals = formValues.decimals, + contractAddress = formValues.contractAddress, + ) } } \ No newline at end of file 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 f61ff6a0b3..64d77526c1 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 @@ -42,7 +42,17 @@ internal object ManageTokensDataModule { @Provides @Singleton - fun provideCustomTokensRepository(): CustomTokensRepository { - return DefaultCustomTokensRepository() + fun provideCustomTokensRepository( + tangemTechApi: TangemTechApi, + userWalletsStore: UserWalletsStore, + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + ): CustomTokensRepository { + return DefaultCustomTokensRepository( + tangemTechApi, + userWalletsStore, + appPreferencesStore, + dispatchers, + ) } } \ No newline at end of file