diff --git a/features/learn2earn/impl/build.gradle.kts b/features/learn2earn/impl/build.gradle.kts index b9fb7f48a6..dcb142ae2d 100644 --- a/features/learn2earn/impl/build.gradle.kts +++ b/features/learn2earn/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(project(":data:common")) implementation(project(":libs:auth")) implementation(project(":libs:crypto")) + implementation(projects.domain.tokens.models) implementation(deps.material) diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 67d016525d..2d753c6fa2 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.legacy) implementation(projects.domain.models) + implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) /** Data */ @@ -32,5 +33,6 @@ dependencies { /** DI */ implementation(deps.hilt.android) + kapt(deps.hilt.kapt) } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index f63f42e5f3..a3d9b7aa92 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Approver import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.extensions.Result +import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.models.request.PairsRequestBody @@ -15,8 +16,10 @@ import com.tangem.datasource.api.oneinch.errors.OneIncResponseException import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.domain.SwapRepository @@ -40,6 +43,7 @@ internal class SwapRepositoryImpl @Inject constructor( private val coroutineDispatcher: CoroutineDispatcherProvider, private val configManager: ConfigManager, private val walletManagersFacade: WalletManagersFacade, + private val walletsStateHolder: WalletsStateHolder, ) : SwapRepository { private val tokensConverter = TokensConverter() @@ -47,6 +51,7 @@ internal class SwapRepositoryImpl @Inject constructor( private val leastTokenInfoConverter = LeastTokenInfoConverter() private val swapPairInfoConverter = SwapPairInfoConverter() private val swapProviderConverter = SwapProviderConverter() + private val cryptoCurrencyFactory = CryptoCurrencyFactory() override suspend fun getPairs( initialCurrency: LeastTokenInfo, @@ -304,6 +309,23 @@ internal class SwapRepositoryImpl @Inject constructor( ) } + override fun getNativeTokenForNetwork(networkId: String): CryptoCurrency { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + + return requireNotNull( + cryptoCurrencyFactory.createCoin( + blockchain = blockchain, + extraDerivationPath = null, + derivationStyleProvider = requireNotNull( + walletsStateHolder.userWalletsListManager + ?.selectedUserWalletSync + ?.scanResponse + ?.derivationStyleProvider, + ), + ), + ) + } + companion object { // TODO("get this ids from blockchain enum later") private const val OPTIMISM_ID = "optimistic-ethereum" diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 89506db425..a70de3e545 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.api.oneinch.OneInchErrorsHandler import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.config.ConfigManager import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.feature.swap.SwapRepositoryImpl import com.tangem.feature.swap.domain.SwapRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -29,6 +30,7 @@ class SwapDataModule { coroutineDispatcher: CoroutineDispatcherProvider, configManager: ConfigManager, walletManagerFacade: WalletManagersFacade, + walletsStateHolder: WalletsStateHolder, ): SwapRepository { return SwapRepositoryImpl( tangemTechApi = tangemTechApi, @@ -38,6 +40,7 @@ class SwapDataModule { coroutineDispatcher = coroutineDispatcher, configManager = configManager, walletManagersFacade = walletManagerFacade, + walletsStateHolder = walletsStateHolder, ) } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 4a196f0569..9a6d547675 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -25,14 +25,6 @@ interface SwapInteractor { */ suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensStateExpress - /** - * Find specific token by id, null if not found - * - * @param id token id - * @return [CryptoCurrency] or null - */ - fun findTokenById(id: String): CryptoCurrency? - /** * Gives permission to swap, this starts scan card process * diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 28de49180c..a43fd0295d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -6,8 +6,10 @@ import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.swap.domain.cache.SwapDataCache @@ -15,7 +17,6 @@ import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel import com.tangem.feature.swap.domain.models.domain.* -import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles @@ -44,6 +45,7 @@ internal class SwapInteractorImpl @Inject constructor( private val walletFeatureToggles: WalletFeatureToggles, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, + private val quotesRepository: QuotesRepository, ) : SwapInteractor { // TODO: Move to DI @@ -187,14 +189,6 @@ internal class SwapInteractorImpl @Inject constructor( ) } - @Deprecated("used in old swap mechanism") - override fun findTokenById(id: String): CryptoCurrency? { - val tokensInWallet = cache.getInWalletTokens() - val loadedTokens = cache.getLoadedTokens() - return tokensInWallet.firstOrNull { it.token.id.value == id }?.token - ?: loadedTokens.firstOrNull { it.token.id.value == id }?.token - } - @Deprecated("used in old swap mechanism") override suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState { val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) { @@ -389,32 +383,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - @Suppress("UnusedPrivateMember") - @Deprecated("used in old swap mechanism") - private fun getTokensWithBalance( - tokens: List, - balances: Map, - rates: Map, - appCurrency: ProxyFiatCurrency, - ): List { - return tokens.map { - val balance = balances[it.symbol] - TokenWithBalance( - token = it, - tokenBalanceData = TokenBalanceData( - amount = balance?.let { amount -> - amountFormatter.formatSwapAmountToUI(amount, it.symbol) - }, - amountEquivalent = balance?.value?.toFiatString( - rateValue = rates[it.id]?.toBigDecimal() ?: BigDecimal.ZERO, - fiatCurrencyName = appCurrency.symbol, - formatWithSpaces = true, - ), - ), - ) - } - } - private suspend fun isAllowedToSpend(networkId: String, fromToken: CryptoCurrency, amount: SwapAmount): Boolean { if (fromToken is CryptoCurrency.Coin) return true return getSelectedWalletSyncUseCase().fold( @@ -537,9 +505,9 @@ internal class SwapInteractorImpl @Inject constructor( private suspend fun getFormattedFiatFees(networkId: String, vararg fees: BigDecimal): List { val appCurrency = userWalletManager.getUserAppCurrency() - val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) - val rates = repository.getRates(appCurrency.code, listOf(nativeToken.id)) - return rates[nativeToken.id]?.toBigDecimal()?.let { rate -> + val nativeToken = repository.getNativeTokenForNetwork(networkId) + val rates = getQuotes(nativeToken.id) + return rates[nativeToken.id]?.fiatRate?.let { rate -> fees.map { fee -> " (${fee.toFiatString(rate, appCurrency.symbol, true)})" } @@ -627,30 +595,27 @@ internal class SwapInteractorImpl @Inject constructor( ): SwapState.QuotesLoadedState { val fromToken = fromTokenStatus.currency val toToken = toTokenStatus.currency - val appCurrency = userWalletManager.getUserAppCurrency() - val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) - val rates = repository.getRates( - appCurrency.code, - listOf(fromToken.network.backendId, toToken.network.backendId, nativeToken.id), - ) + val nativeToken = repository.getNativeTokenForNetwork(networkId) + + val rates = getQuotes(fromToken.id, toToken.id, nativeToken.id) return SwapState.QuotesLoadedState( fromTokenInfo = TokenSwapInfo( tokenAmount = fromTokenAmount, cryptoCurrencyStatus = fromTokenStatus, - amountFiat = rates[fromToken.network.backendId]?.toBigDecimal()?.multiply(fromTokenAmount.value) + amountFiat = rates[fromToken.id]?.fiatRate?.multiply(fromTokenAmount.value) ?: BigDecimal.ZERO, ), toTokenInfo = TokenSwapInfo( tokenAmount = toTokenAmount, cryptoCurrencyStatus = toTokenStatus, - amountFiat = rates[toToken.network.backendId]?.toBigDecimal()?.multiply(toTokenAmount.value) + amountFiat = rates[toToken.id]?.fiatRate?.multiply(toTokenAmount.value) ?: BigDecimal.ZERO, ), priceImpact = calculatePriceImpact( fromTokenAmount = fromTokenAmount.value, - fromRate = rates[fromToken.network.backendId] ?: 0.0, + fromRate = rates[fromToken.id]?.fiatRate?.toDouble() ?: 0.0, toTokenAmount = toTokenAmount.value, - toRate = rates[toToken.network.backendId] ?: 0.0, + toRate = rates[toToken.id]?.fiatRate?.toDouble() ?: 0.0, ), networkCurrency = userWalletManager.getNetworkCurrency(networkId), swapDataModel = swapStateData, @@ -687,7 +652,7 @@ internal class SwapInteractorImpl @Inject constructor( val feeData = transactionManager.getFee( networkId = networkId, amountToSend = BigDecimal.ZERO, - currencyToSend = userWalletManager.getNativeTokenForNetwork(networkId), + currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), destinationAddress = getTokenAddress(fromToken), increaseBy = INCREASE_GAS_LIMIT_BY, data = transactionData, @@ -863,6 +828,14 @@ internal class SwapInteractorImpl @Inject constructor( ) } + private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map { + val set = quotesRepository.getQuotesSync(ids.toSet(), false) + + return ids + .mapNotNull { id -> set.find { it.rawCurrencyId == id.rawCurrencyId }?.let { id to it } } + .toMap() + } + companion object { private const val DEFAULT_SLIPPAGE = 2 diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index f8664afb69..60d4275364 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -78,4 +78,6 @@ interface SwapRepository { providerId: String, rateType: RateType, ): ExchangeQuote + + fun getNativeTokenForNetwork(networkId: String): CryptoCurrency } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 4890b5ca49..ce4956ba90 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,20 +1,15 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress -import java.math.BigDecimal interface SwapDataCache { - fun cacheAvailableToSwapTokens(networkId: String, tokens: List) fun cacheInWalletTokens(tokens: List) fun cacheLoadedTokens(tokens: List) fun cacheBalances(networkId: String, derivationPath: String?, balances: Map) - fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) - fun getAvailableTokens(networkId: String): List + fun getInWalletTokens(): List fun getLoadedTokens(): List fun getBalanceForToken(networkId: String, derivationPath: String?, symbol: String): SwapAmount? - fun getLastFeeForNetwork(networkId: String): BigDecimal? } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index 2a5d7535ca..92c051e246 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,22 +1,14 @@ package com.tangem.feature.swap.domain.cache import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress -import java.math.BigDecimal class SwapDataCacheImpl : SwapDataCache { - private val availableTokensForNetwork: MutableMap> = mutableMapOf() - private val feesForNetworks: MutableMap = mutableMapOf() private val tokensBalances: MutableMap> = mutableMapOf() private val lastInWalletTokens = mutableListOf() private val lastLoadedTokens = mutableListOf() - override fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) { - feesForNetworks[networkId] = fee - } - override fun cacheInWalletTokens(tokens: List) { lastInWalletTokens.clear() lastInWalletTokens.addAll(tokens) @@ -43,18 +35,6 @@ class SwapDataCacheImpl : SwapDataCache { tokensBalances[createKeyFrom(networkId, derivationPath)] = balances } - override fun cacheAvailableToSwapTokens(networkId: String, tokens: List) { - availableTokensForNetwork[networkId] = tokens - } - - override fun getLastFeeForNetwork(networkId: String): BigDecimal? { - return feesForNetworks[networkId] - } - - override fun getAvailableTokens(networkId: String): List { - return availableTokensForNetwork.getOrElse(networkId) { emptyList() } - } - private fun createKeyFrom(networkId: String, derivationPath: String?): String { return "$networkId;$derivationPath" } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 8845c68949..bdcfad48f9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -35,6 +35,7 @@ class SwapDomainModule { walletFeatureToggles: WalletFeatureToggles, @SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, @SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, + quotesRepository: QuotesRepository, ): SwapInteractor { return SwapInteractorImpl( transactionManager = transactionManager, @@ -47,6 +48,7 @@ class SwapDomainModule { walletFeatureToggles = walletFeatureToggles, getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, + quotesRepository = quotesRepository, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 4089832235..4033c21dbc 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -34,7 +34,7 @@ import java.math.RoundingMode /** * State builder creates a specific states for SwapScreen */ -@Suppress("LargeClass") +@Suppress("LargeClass", "TooManyFunctions") internal class StateBuilder( private val actions: UiActions, private val isBalanceHiddenProvider: Provider, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 692d6a4e57..9d59406885 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -2,19 +2,15 @@ package com.tangem.feature.swap.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig -import com.tangem.feature.swap.presentation.R @OptIn(ExperimentalLayoutApi::class) @Composable diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 15fa430b17..cca50c074d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -530,7 +530,7 @@ internal class SwapViewModel @Inject constructor( } } - @Suppress("LongMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun createUiActions(): UiActions { return UiActions( onSearchEntered = { onSearchEntered(it) }, diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index 248cc20bd1..f8a413d31d 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -1,10 +1,12 @@ plugins { - alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) id("configuration") } dependencies { - /** Coroutines */ implementation(deps.kotlin.coroutines) + implementation(projects.domain.tokens.models) + } \ No newline at end of file