Updated on 2026-08-14
This commit is contained in:
parent
3e2de9fd6b
commit
357955b1dd
10 changed files with 359 additions and 0 deletions
|
|
@ -2,7 +2,11 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.swap.SwapErrorResolver
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.SwapTransactionRepository
|
||||
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
|
||||
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
|
||||
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
|
||||
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
|
||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -35,4 +39,38 @@ internal object SwapDomainModule {
|
|||
swapErrorResolver = swapErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetSwapPairsUseCase(
|
||||
swapRepositoryV2: SwapRepositoryV2,
|
||||
swapErrorResolver: SwapErrorResolver,
|
||||
): GetSwapPairsUseCase {
|
||||
return GetSwapPairsUseCase(
|
||||
swapRepositoryV2 = swapRepositoryV2,
|
||||
swapErrorResolver = swapErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSelectInitialPairUseCase(
|
||||
swapTransactionRepository: SwapTransactionRepository,
|
||||
): SelectInitialPairUseCase {
|
||||
return SelectInitialPairUseCase(
|
||||
swapTransactionRepository = swapTransactionRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetSwapQuoteUseCase(
|
||||
swapRepositoryV2: SwapRepositoryV2,
|
||||
swapErrorResolver: SwapErrorResolver,
|
||||
): GetSwapQuoteUseCase {
|
||||
return GetSwapQuoteUseCase(
|
||||
swapRepositoryV2 = swapRepositoryV2,
|
||||
swapErrorResolver = swapErrorResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
|||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
@Deprecated("Use ExpressErrorConverter")
|
||||
internal class OnrampErrorConverter(
|
||||
private val jsonAdapter: JsonAdapter<ExpressErrorResponse>,
|
||||
) : Converter<String, OnrampError> {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.express.ExpressRepository
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapPairModel
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -22,6 +24,7 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -34,6 +37,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
) : SwapRepositoryV2 {
|
||||
|
||||
private val tokenInfoConverter = TokenInfoConverter()
|
||||
|
||||
override suspend fun getPairs(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: CryptoCurrency,
|
||||
|
|
@ -115,6 +119,40 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
}.awaitAll().filterNotNull()
|
||||
}
|
||||
|
||||
override suspend fun getSwapQuote(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrency: CryptoCurrency,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
fromAmount: BigDecimal,
|
||||
provider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
): SwapQuoteModel = withContext(coroutineDispatcher.io) {
|
||||
val response = tangemExpressApi.getExchangeQuote(
|
||||
fromAmount = fromAmount.movePointRight(fromCryptoCurrency.decimals).toString(),
|
||||
fromNetwork = fromCryptoCurrency.network.backendId,
|
||||
fromContractAddress = fromCryptoCurrency.getContractAddress(),
|
||||
fromDecimals = fromCryptoCurrency.decimals,
|
||||
toNetwork = toCryptoCurrency.network.backendId,
|
||||
toContractAddress = toCryptoCurrency.getContractAddress(),
|
||||
toDecimals = toCryptoCurrency.decimals,
|
||||
providerId = provider.providerId,
|
||||
rateType = rateType.name.lowercase(),
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
),
|
||||
).getOrThrow()
|
||||
|
||||
val toTokenAmount = requireNotNull(response.toAmount.toBigDecimalOrNull()?.movePointLeft(response.toDecimals))
|
||||
|
||||
return@withContext SwapQuoteModel(
|
||||
provider = provider,
|
||||
toTokenAmount = toTokenAmount,
|
||||
allowanceContract = response.allowanceContract,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun CoroutineScope.getPairsInternal(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: CryptoCurrency,
|
||||
|
|
|
|||
|
|
@ -3,17 +3,43 @@ package com.tangem.domain.swap.models
|
|||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
||||
/**
|
||||
* Model of currencies available to swap
|
||||
*
|
||||
* @param fromGroup group of currencies available swap from
|
||||
* @param toGroup group of currencies available swap to
|
||||
*/
|
||||
data class SwapCurrencies(
|
||||
val fromGroup: SwapCurrenciesGroup,
|
||||
val toGroup: SwapCurrenciesGroup,
|
||||
)
|
||||
|
||||
/**
|
||||
* Return swap group depending on [swapDirection]
|
||||
*/
|
||||
fun SwapCurrencies.getGroupWithReverse(swapDirection: SwapDirection): SwapCurrenciesGroup {
|
||||
return when (swapDirection) {
|
||||
SwapDirection.Reverse -> fromGroup
|
||||
SwapDirection.Direct -> toGroup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap group model
|
||||
*
|
||||
* @param available list of available currencies to swap
|
||||
* @param available list of unavailable currencies to swap
|
||||
* @param afterSearch flag indicates whether user searched token
|
||||
*/
|
||||
data class SwapCurrenciesGroup(
|
||||
val available: List<SwapCryptoCurrency>,
|
||||
val unavailable: List<SwapCryptoCurrency>,
|
||||
val afterSearch: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Crypto currency with providers to swap
|
||||
*/
|
||||
data class SwapCryptoCurrency(
|
||||
val currencyStatus: CryptoCurrencyStatus,
|
||||
val providers: List<ExpressProvider>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.swap.models
|
||||
|
||||
import com.tangem.domain.swap.models.SwapDirection.Direct
|
||||
import com.tangem.domain.swap.models.SwapDirection.Reverse
|
||||
|
||||
/**
|
||||
* Swap direction
|
||||
*
|
||||
* Initial currency can be swap to or from.
|
||||
* If swap being swap from direction is [Direct],
|
||||
* Otherwise direction is [Reverse]
|
||||
*/
|
||||
enum class SwapDirection {
|
||||
Direct,
|
||||
Reverse,
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.swap.models
|
||||
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Quote model holds data about current amounts of swaps and fees
|
||||
*
|
||||
* @property provider swap provider
|
||||
* @property toTokenAmount amount of token you want to receive
|
||||
* @property allowanceContract whether swap occurs via third token
|
||||
*/
|
||||
data class SwapQuoteModel(
|
||||
val provider: ExpressProvider,
|
||||
val toTokenAmount: BigDecimal,
|
||||
val allowanceContract: String?,
|
||||
)
|
||||
|
|
@ -1,10 +1,17 @@
|
|||
package com.tangem.domain.swap
|
||||
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapPairModel
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Swap repository
|
||||
*/
|
||||
interface SwapRepositoryV2 {
|
||||
|
||||
/**
|
||||
|
|
@ -26,4 +33,24 @@ interface SwapRepositoryV2 {
|
|||
initialCurrency: CryptoCurrency,
|
||||
cryptoCurrencyList: List<CryptoCurrency>,
|
||||
): List<SwapPairModel>
|
||||
|
||||
/**
|
||||
* Returns swap quotes on selected pair
|
||||
*
|
||||
* @param userWallet selected user wallet
|
||||
* @param fromCryptoCurrency currency being swapped from
|
||||
* @param toCryptoCurrency currency being swapped to
|
||||
* @param fromAmount swap amount
|
||||
* @param provider selected express provider
|
||||
* @param rateType rate type
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun getSwapQuote(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrency: CryptoCurrency,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
fromAmount: BigDecimal,
|
||||
provider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
): SwapQuoteModel
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.domain.swap.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.SwapErrorResolver
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapCryptoCurrency
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapCurrenciesGroup
|
||||
import com.tangem.domain.swap.models.SwapPairModel
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
/**
|
||||
* Get list of swap pairs
|
||||
*/
|
||||
class GetSwapPairsUseCase(
|
||||
private val swapRepositoryV2: SwapRepositoryV2,
|
||||
private val swapErrorResolver: SwapErrorResolver,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param userWallet selected user wallet
|
||||
* @param initialCurrency initial currency to swap (to or from)
|
||||
* @param cryptoCurrencyStatusList list of added cryptocurrencies
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: CryptoCurrency,
|
||||
cryptoCurrencyStatusList: List<CryptoCurrencyStatus>,
|
||||
) = Either.catch {
|
||||
val pairs = swapRepositoryV2.getPairs(
|
||||
userWallet = userWallet,
|
||||
initialCurrency = initialCurrency,
|
||||
cryptoCurrencyStatusList = cryptoCurrencyStatusList,
|
||||
)
|
||||
|
||||
val fromGroup = pairs.groupPairs(
|
||||
initialCurrency = initialCurrency,
|
||||
filteringCurrency = { it.from },
|
||||
groupingCurrency = { it.to },
|
||||
cryptoCurrencyStatusList = cryptoCurrencyStatusList,
|
||||
)
|
||||
val toGroup = pairs.groupPairs(
|
||||
initialCurrency = initialCurrency,
|
||||
filteringCurrency = { it.to },
|
||||
groupingCurrency = { it.from },
|
||||
cryptoCurrencyStatusList = cryptoCurrencyStatusList,
|
||||
)
|
||||
|
||||
SwapCurrencies(
|
||||
fromGroup = fromGroup,
|
||||
toGroup = toGroup,
|
||||
)
|
||||
}.mapLeft(swapErrorResolver::resolve)
|
||||
|
||||
private fun List<SwapPairModel>.groupPairs(
|
||||
initialCurrency: CryptoCurrency,
|
||||
filteringCurrency: (SwapPairModel) -> CryptoCurrencyStatus,
|
||||
groupingCurrency: (SwapPairModel) -> CryptoCurrencyStatus,
|
||||
cryptoCurrencyStatusList: List<CryptoCurrencyStatus>,
|
||||
): SwapCurrenciesGroup {
|
||||
val availableCryptoCurrencies = filter { pair -> filteringCurrency(pair).currency.id == initialCurrency.id }
|
||||
// Search available to swap currency
|
||||
.filter { pair ->
|
||||
cryptoCurrencyStatusList.any { currencyStatus ->
|
||||
currencyStatus.currency.id == pair.to.currency.id
|
||||
}
|
||||
}.map { pair -> SwapCryptoCurrency(groupingCurrency(pair), pair.providers) }
|
||||
|
||||
val unavailableCryptoCurrencies =
|
||||
cryptoCurrencyStatusList - availableCryptoCurrencies.map { it.currencyStatus }.toSet()
|
||||
|
||||
return SwapCurrenciesGroup(
|
||||
available = availableCryptoCurrencies,
|
||||
unavailable = unavailableCryptoCurrencies.map { pair -> SwapCryptoCurrency(pair, emptyList()) },
|
||||
afterSearch = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.domain.swap.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.SwapErrorResolver
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Get swap quote for selected pair
|
||||
*/
|
||||
class GetSwapQuoteUseCase(
|
||||
private val swapRepositoryV2: SwapRepositoryV2,
|
||||
private val swapErrorResolver: SwapErrorResolver,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param userWallet selected user wallet
|
||||
* @param fromCryptoCurrency currency swap from
|
||||
* @param toCryptoCurrency currency swap to
|
||||
* @param fromAmount swap amount
|
||||
* @param provider swap provider
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrency: CryptoCurrency,
|
||||
toCryptoCurrency: CryptoCurrency,
|
||||
fromAmount: BigDecimal,
|
||||
provider: ExpressProvider,
|
||||
): Either<ExpressError, SwapQuoteModel> = Either.catch {
|
||||
swapRepositoryV2.getSwapQuote(
|
||||
userWallet = userWallet,
|
||||
fromCryptoCurrency = fromCryptoCurrency,
|
||||
toCryptoCurrency = toCryptoCurrency,
|
||||
fromAmount = fromAmount,
|
||||
provider = provider,
|
||||
rateType = ExpressRateType.Float, // todo rate type
|
||||
)
|
||||
}.mapLeft(swapErrorResolver::resolve)
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.domain.swap.usecase
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.SwapTransactionRepository
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.swap.models.SwapCurrenciesGroup
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.swap.models.getGroupWithReverse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
/**
|
||||
* Select initial pair to swap
|
||||
*/
|
||||
class SelectInitialPairUseCase(
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param userWallet selected user wallet
|
||||
* @param primaryCryptoCurrency initial currency
|
||||
* @param secondaryCryptoCurrency selected currency
|
||||
* @param swapCurrencies list of currencies with providers
|
||||
* @param swapDirection swap direction
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
primaryCryptoCurrency: CryptoCurrency,
|
||||
secondaryCryptoCurrency: CryptoCurrency?,
|
||||
swapCurrencies: SwapCurrencies,
|
||||
swapDirection: SwapDirection,
|
||||
): CryptoCurrencyStatus? {
|
||||
val swapCurrenciesGroup = swapCurrencies.getGroupWithReverse(swapDirection)
|
||||
return tryToGetAlreadySelectedCurrency(secondaryCryptoCurrency, swapCurrenciesGroup)
|
||||
?: tryGetFromCache(userWallet, primaryCryptoCurrency, swapCurrenciesGroup)
|
||||
?: tryGetWithMaxAmount(swapCurrenciesGroup)
|
||||
?: swapCurrenciesGroup.available.firstOrNull()?.currencyStatus
|
||||
}
|
||||
|
||||
private fun tryToGetAlreadySelectedCurrency(
|
||||
secondaryCryptoCurrency: CryptoCurrency?,
|
||||
swapCurrenciesGroup: SwapCurrenciesGroup,
|
||||
): CryptoCurrencyStatus? {
|
||||
return secondaryCryptoCurrency?.let {
|
||||
swapCurrenciesGroup.available.firstOrNull {
|
||||
secondaryCryptoCurrency.id == it.currencyStatus.currency.id
|
||||
}?.currencyStatus
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryGetFromCache(
|
||||
userWallet: UserWallet,
|
||||
primaryCryptoCurrency: CryptoCurrency,
|
||||
swapCurrenciesGroup: SwapCurrenciesGroup,
|
||||
): CryptoCurrencyStatus? {
|
||||
val id = swapTransactionRepository.getLastSwappedCryptoCurrencyId(userWallet.walletId) ?: return null
|
||||
|
||||
return if (id != primaryCryptoCurrency.id.value) {
|
||||
swapCurrenciesGroup.available.find { it.currencyStatus.currency.id.value == id }?.currencyStatus
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryGetWithMaxAmount(swapCurrenciesGroup: SwapCurrenciesGroup): CryptoCurrencyStatus? {
|
||||
return swapCurrenciesGroup.available.maxByOrNull {
|
||||
it.currencyStatus.value.fiatAmount.orZero()
|
||||
}?.currencyStatus
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue