Updated on 2026-08-14
This commit is contained in:
parent
cf4dc83d0d
commit
83571f3bd5
19 changed files with 360 additions and 57 deletions
|
|
@ -66,6 +66,12 @@ class TransactionManagerImpl(
|
|||
return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found")
|
||||
}
|
||||
|
||||
override fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val gasPriceValue = requireNotNull(gasPrice.toLongOrNull()) { "gasprice should be Long" }
|
||||
return (gasPriceValue * estimatedGas).toBigDecimal().movePointLeft(blockchain.decimals())
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override suspend fun getFee(
|
||||
networkId: String,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,11 @@ class UserWalletManagerImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override fun getCurrencyByNetworkId(networkId: String): String {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
return blockchain.currency
|
||||
}
|
||||
|
||||
private fun addNativeTokenToWalletAction(token: NativeToken, card: CardDTO): Action {
|
||||
val blockchain = Blockchain.fromNetworkId(token.networkId)
|
||||
val scanResponse = appStateHolder.scanResponse
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class OneInchApisModule {
|
|||
val apiFactory = OneInchApiFactory()
|
||||
apiFactory.putApi(ETH_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_ETH_PATH, moshi))
|
||||
apiFactory.putApi(BSC_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_BSC_PATH, moshi))
|
||||
apiFactory.putApi(POLYGON_NETWORK, createOneInchApiWithUrl(ONE_INCH_BASE_URL + ONE_INCH_POLYGON_PATH, moshi))
|
||||
return apiFactory
|
||||
}
|
||||
|
||||
|
|
@ -45,8 +46,10 @@ class OneInchApisModule {
|
|||
private const val ONE_INCH_BASE_URL = "https://api.1inch.io/v5.0/"
|
||||
private const val ONE_INCH_ETH_PATH = "1/"
|
||||
private const val ONE_INCH_BSC_PATH = "56/"
|
||||
private const val ONE_INCH_POLYGON_PATH = "137/"
|
||||
|
||||
private const val ETH_NETWORK = "ethereum"
|
||||
private const val BSC_NETWORK = "binance-smart-chain"
|
||||
private const val POLYGON_NETWORK = "polygon-pos"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.datasource.api.oneinch.models.SwapResponse
|
|||
import com.tangem.datasource.api.oneinch.models.TransactionDto
|
||||
import com.tangem.feature.swap.domain.models.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.TransactionModel
|
||||
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -13,8 +14,8 @@ class SwapConverter @Inject constructor() : Converter<SwapResponse, SwapDataMode
|
|||
return SwapDataModel(
|
||||
fromTokenAddress = value.fromToken.address,
|
||||
toTokenAddress = value.toToken.address,
|
||||
toTokenAmount = value.fromTokenAmount,
|
||||
fromTokenAmount = value.toTokenAmount,
|
||||
fromTokenAmount = createFromAmountWithOffset(value.fromTokenAmount, value.fromToken.decimals),
|
||||
toTokenAmount = createFromAmountWithOffset(value.toTokenAmount, value.toToken.decimals),
|
||||
transaction = convertTransaction(value.transaction),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
interface AllowPermissionsHandler {
|
||||
|
||||
fun addAddressToInProgress(tokenAddress: String)
|
||||
fun removeAddressFromProgress(tokenAddress: String)
|
||||
fun isAddressAllowanceInProgress(tokenAddress: String): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
class AllowPermissionsHandlerImpl : AllowPermissionsHandler {
|
||||
|
||||
//todo maybe need to save in store
|
||||
private val allowPermissionsInProgress = mutableSetOf<String>()
|
||||
|
||||
override fun addAddressToInProgress(tokenAddress: String) {
|
||||
allowPermissionsInProgress.add(tokenAddress)
|
||||
}
|
||||
|
||||
override fun removeAddressFromProgress(tokenAddress: String) {
|
||||
allowPermissionsInProgress.remove(tokenAddress)
|
||||
}
|
||||
|
||||
override fun isAddressAllowanceInProgress(tokenAddress: String): Boolean {
|
||||
return allowPermissionsInProgress.contains(tokenAddress)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,11 @@ interface SwapInteractor {
|
|||
|
||||
suspend fun getTokensToSwap(networkId: String): List<Currency>
|
||||
|
||||
/**
|
||||
* Give permission to swap
|
||||
*
|
||||
* @param tokenToApprove use token which you want to swap
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun givePermissionToSwap(tokenToApprove: Currency)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import com.tangem.feature.swap.domain.cache.SwapDataCache
|
||||
import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter
|
||||
import com.tangem.feature.swap.domain.models.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.PermissionDataState
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.SwapState
|
||||
import com.tangem.feature.swap.domain.models.TransactionModel
|
||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import com.tangem.utils.toFiatString
|
||||
import com.tangem.utils.toFormattedString
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -21,6 +23,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private val userWalletManager: UserWalletManager,
|
||||
private val repository: SwapRepository,
|
||||
private val cache: SwapDataCache,
|
||||
private val allowPermissionsHandler: AllowPermissionsHandler,
|
||||
) : SwapInteractor {
|
||||
|
||||
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
|
||||
|
|
@ -40,9 +43,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
if (tokenToApprove is Currency.NonNativeToken) {
|
||||
val estimatedGas =
|
||||
requireNotNull(cache.getLastQuote()?.estimatedGas) { "estimatedGas not found call findBestQuote" }
|
||||
val transactionData = repository.dataToApprove(networkId, tokenToApprove.contractAddress)
|
||||
val transactionData =
|
||||
requireNotNull(cache.getApproveTransactionData()) { "getApproveTransactionData not found, call findQuotes" }
|
||||
val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice")
|
||||
val fee = gasPrice.multiply(estimatedGas.toBigDecimal()).movePointLeft(tokenToApprove.decimalCount)
|
||||
val fee = transactionManager.calculateFee(networkId, gasPrice.toPlainString(), estimatedGas)
|
||||
val result = transactionManager.sendTransaction(
|
||||
networkId = networkId,
|
||||
amountToSend = BigDecimal.ZERO,
|
||||
|
|
@ -51,43 +55,46 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
destinationAddress = transactionData.toAddress,
|
||||
dataToSign = transactionData.data,
|
||||
)
|
||||
when (result) {
|
||||
SendTxResult.Success -> {
|
||||
allowPermissionsHandler.addAddressToInProgress(tokenToApprove.contractAddress)
|
||||
}
|
||||
SendTxResult.UserCancelledError -> TODO()
|
||||
is SendTxResult.BlockchainSdkError -> TODO()
|
||||
is SendTxResult.TangemSdkError -> TODO()
|
||||
is SendTxResult.UnknownError -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findBestQuote(fromToken: Currency, toToken: Currency, amount: SwapAmount): SwapState {
|
||||
val networkId = cache.getNetworkId()
|
||||
val networkId = requireNotNull(cache.getNetworkId()) { "no networkId found, please call getTokensToSwap first" }
|
||||
val fromTokenAddress = getTokenAddress(fromToken)
|
||||
val toTokenAddress = getTokenAddress(toToken)
|
||||
val isAllowedToSpend = if (networkId.isNullOrEmpty()) {
|
||||
error("no networkId found, please call getTokensToSwap first")
|
||||
} else {
|
||||
val allowance =
|
||||
repository.checkTokensSpendAllowance(
|
||||
networkId = networkId,
|
||||
tokenAddress = fromTokenAddress,
|
||||
walletAddress = userWalletManager.getWalletAddress(networkId),
|
||||
)
|
||||
allowance.error == DataError.NO_ERROR && allowance.dataModel != "0"
|
||||
}
|
||||
repository.findBestQuote(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount.toStringWithRightOffset(),
|
||||
).let { quotes ->
|
||||
val quoteDataModel = quotes.dataModel
|
||||
if (quoteDataModel != null) {
|
||||
cache.cacheSwapParams(
|
||||
quoteModel = quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend),
|
||||
amount = amount,
|
||||
fromCurrency = fromToken,
|
||||
toCurrency = toToken,
|
||||
)
|
||||
return updateBalances(networkId, fromToken, toToken, quoteDataModel)
|
||||
} else {
|
||||
return SwapState.SwapError(quotes.error)
|
||||
val isAllowedToSpend = checkAllowance(networkId, fromTokenAddress)
|
||||
val isBalanceZero = isNotZeroBalance(fromToken, networkId)
|
||||
return if (isAllowedToSpend && isBalanceZero) {
|
||||
if (allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
|
||||
allowPermissionsHandler.removeAddressFromProgress(toTokenAddress)
|
||||
}
|
||||
loadSwapData(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = amount,
|
||||
)
|
||||
} else {
|
||||
loadQuoteData(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,11 +135,120 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun checkAllowance(networkId: String, fromTokenAddress: String): Boolean {
|
||||
val allowance = repository.checkTokensSpendAllowance(
|
||||
networkId = networkId,
|
||||
tokenAddress = fromTokenAddress,
|
||||
walletAddress = userWalletManager.getWalletAddress(networkId),
|
||||
)
|
||||
return allowance.error == DataError.NO_ERROR && allowance.dataModel != ZERO_BALANCE
|
||||
}
|
||||
|
||||
/**
|
||||
* Load quote data calls only if spend is not allowed for token contract address
|
||||
*/
|
||||
private suspend fun loadQuoteData(
|
||||
networkId: String,
|
||||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
amount: SwapAmount,
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
): SwapState {
|
||||
repository.findBestQuote(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount.toStringWithRightOffset(),
|
||||
).let { quotes ->
|
||||
val quoteDataModel = quotes.dataModel
|
||||
if (quoteDataModel != null) {
|
||||
cache.cacheSwapParams(
|
||||
quoteModel = quoteDataModel,
|
||||
amount = amount,
|
||||
fromCurrency = fromToken,
|
||||
toCurrency = toToken,
|
||||
)
|
||||
val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken))
|
||||
cache.cacheApproveTransactionData(transactionData)
|
||||
val swapState = updateBalances(
|
||||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromTokenAmount = quoteDataModel.fromTokenAmount,
|
||||
toTokenAmount = quoteDataModel.toTokenAmount,
|
||||
fee = transactionManager.calculateFee(
|
||||
networkId = networkId,
|
||||
estimatedGas = quoteDataModel.estimatedGas,
|
||||
gasPrice = transactionData.gasPrice,
|
||||
),
|
||||
isAllowedToSpend = false,
|
||||
)
|
||||
return updatePermissionState(
|
||||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
quotesLoadedState = swapState,
|
||||
estimatedGas = quoteDataModel.estimatedGas,
|
||||
transactionData = transactionData,
|
||||
)
|
||||
} else {
|
||||
return SwapState.SwapError(quotes.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load swap data calls only if spend is allowed for token contract address
|
||||
*/
|
||||
private suspend fun loadSwapData(
|
||||
networkId: String,
|
||||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
amount: SwapAmount,
|
||||
): SwapState {
|
||||
repository.prepareSwapTransaction(
|
||||
networkId = networkId,
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount.toStringWithRightOffset(),
|
||||
slippage = DEFAULT_SLIPPAGE,
|
||||
fromWalletAddress = getWalletAddress(networkId),
|
||||
).let {
|
||||
val swapData = it.dataModel
|
||||
if (swapData != null) {
|
||||
cache.cacheSwapData(swapData)
|
||||
val swapState = updateBalances(
|
||||
networkId = networkId,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromTokenAmount = swapData.fromTokenAmount,
|
||||
toTokenAmount = swapData.toTokenAmount,
|
||||
fee = transactionManager.calculateFee(
|
||||
networkId = networkId,
|
||||
estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS,
|
||||
gasPrice = swapData.transaction.gasPrice,
|
||||
),
|
||||
isAllowedToSpend = false,
|
||||
)
|
||||
return swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
)
|
||||
} else {
|
||||
return SwapState.SwapError(it.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateBalances(
|
||||
networkId: String,
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
quoteModel: QuoteModel,
|
||||
fromTokenAmount: SwapAmount,
|
||||
toTokenAmount: SwapAmount,
|
||||
fee: BigDecimal,
|
||||
isAllowedToSpend: Boolean,
|
||||
): SwapState.QuotesLoadedState {
|
||||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id))
|
||||
|
|
@ -144,25 +260,58 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
it.value.toFormattedString(it.decimals)
|
||||
}
|
||||
return SwapState.QuotesLoadedState(
|
||||
fromTokenAmount = quoteModel.fromTokenAmount,
|
||||
toTokenAmount = quoteModel.toTokenAmount,
|
||||
fromTokenAddress = quoteModel.fromTokenAddress,
|
||||
toTokenAddress = quoteModel.toTokenAddress,
|
||||
estimatedGas = quoteModel.estimatedGas,
|
||||
isAllowedToSpend = quoteModel.isAllowedToSpend,
|
||||
fromTokenAmount = fromTokenAmount,
|
||||
toTokenAmount = toTokenAmount,
|
||||
fromTokenAddress = getTokenAddress(fromToken),
|
||||
toTokenAddress = getTokenAddress(toToken),
|
||||
fee = fee.toPlainString(),
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
fromTokenWalletBalance = fromTokenBalance ?: ZERO_BALANCE,
|
||||
fromTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString(
|
||||
fromTokenFiatBalance = fromTokenAmount.value.toFiatString(
|
||||
rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
appCurrency.symbol,
|
||||
),
|
||||
toTokenWalletBalance = toTokenBalance ?: ZERO_BALANCE,
|
||||
toTokenFiatBalance = quoteModel.fromTokenAmount.value.toFiatString(
|
||||
toTokenFiatBalance = fromTokenAmount.value.toFiatString(
|
||||
rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
appCurrency.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updatePermissionState(
|
||||
networkId: String,
|
||||
fromToken: Currency,
|
||||
quotesLoadedState: SwapState.QuotesLoadedState,
|
||||
estimatedGas: Int,
|
||||
transactionData: ApproveModel,
|
||||
): SwapState.QuotesLoadedState {
|
||||
if (allowPermissionsHandler.isAddressAllowanceInProgress(fromToken.networkId)) {
|
||||
return quotesLoadedState.copy(
|
||||
permissionState = PermissionDataState.PermissionLoading,
|
||||
)
|
||||
}
|
||||
return quotesLoadedState.copy(
|
||||
permissionState = PermissionDataState.PermissionReadyForRequest(
|
||||
currency = userWalletManager.getCurrencyByNetworkId(networkId),
|
||||
amount = "infinite", //FIXME
|
||||
walletAddress = getWalletAddress(networkId),
|
||||
spenderAddress = transactionData.toAddress,
|
||||
fee = transactionManager.calculateFee(
|
||||
networkId = networkId,
|
||||
gasPrice = transactionData.gasPrice,
|
||||
estimatedGas = estimatedGas,
|
||||
).toPlainString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun isNotZeroBalance(fromToken: Currency, networkId: String): Boolean {
|
||||
/** to compare [BigDecimal] use only comparator */
|
||||
return (userWalletManager.getCurrentWalletTokensBalance(networkId)[fromToken.symbol]?.value
|
||||
?: BigDecimal.ZERO).compareTo(BigDecimal.ZERO) != 0
|
||||
}
|
||||
|
||||
private fun getWalletAddress(networkId: String): String {
|
||||
return userWalletManager.getWalletAddress(networkId)
|
||||
}
|
||||
|
|
@ -185,5 +334,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
companion object {
|
||||
private const val DEFAULT_SLIPPAGE = 2
|
||||
private const val ZERO_BALANCE = "0"
|
||||
private const val DEFAULT_GAS = 300000
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,23 @@
|
|||
package com.tangem.feature.swap.domain.cache
|
||||
|
||||
import com.tangem.feature.swap.domain.models.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies
|
||||
|
||||
interface SwapDataCache {
|
||||
|
||||
fun cacheNetworkId(networkId: String)
|
||||
fun cacheSwapParams(quoteModel: QuoteModel, amount: SwapAmount, fromCurrency: Currency, toCurrency: Currency)
|
||||
fun cacheApproveTransactionData(approve: ApproveModel)
|
||||
fun cacheSwapData(swapDataModel: SwapDataModel)
|
||||
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
|
||||
fun getAvailableTokens(networkId: String): List<Currency>
|
||||
fun getApproveTransactionData(): ApproveModel?
|
||||
fun getLastQuote(): QuoteModel?
|
||||
fun getLastSwapData(): SwapDataModel?
|
||||
fun getAmountToSwap(): SwapAmount?
|
||||
fun getNetworkId(): String?
|
||||
fun getExchangeCurrencies(): ExchangeCurrencies?
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.feature.swap.domain.cache
|
||||
|
||||
import com.tangem.feature.swap.domain.models.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.cache.ExchangeCurrencies
|
||||
import com.tangem.feature.swap.domain.models.cache.SwapDataHolder
|
||||
|
||||
|
|
@ -28,6 +30,18 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
)
|
||||
}
|
||||
|
||||
override fun cacheSwapData(swapDataModel: SwapDataModel) {
|
||||
lastDataForSwap = lastDataForSwap.copy(swapModel = swapDataModel)
|
||||
}
|
||||
|
||||
override fun getLastSwapData(): SwapDataModel? {
|
||||
return lastDataForSwap.swapModel
|
||||
}
|
||||
|
||||
override fun cacheApproveTransactionData(approve: ApproveModel) {
|
||||
lastDataForSwap = lastDataForSwap.copy(approveTxModel = approve)
|
||||
}
|
||||
|
||||
override fun getExchangeCurrencies(): ExchangeCurrencies? {
|
||||
return lastDataForSwap.exchangeCurrencies
|
||||
}
|
||||
|
|
@ -48,6 +62,10 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
return availableTokensForNetwork.getOrElse(networkId) { emptyList() }
|
||||
}
|
||||
|
||||
override fun getApproveTransactionData(): ApproveModel? {
|
||||
return lastDataForSwap.approveTxModel
|
||||
}
|
||||
|
||||
override fun getLastQuote(): QuoteModel? {
|
||||
return lastDataForSwap.quoteModel
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.domain.di
|
||||
|
||||
import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractorImpl
|
||||
import com.tangem.feature.swap.domain.SwapRepository
|
||||
|
|
@ -28,6 +29,7 @@ class SwapDomainModule {
|
|||
userWalletManager = userWalletManager,
|
||||
repository = swapRepository,
|
||||
cache = SwapDataCacheImpl(),
|
||||
allowPermissionsHandler = AllowPermissionsHandlerImpl(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ package com.tangem.feature.swap.domain.models
|
|||
* @property fromTokenAddress address token you want to exchange
|
||||
* @property toTokenAddress address token you want to receive
|
||||
* @property estimatedGas fee
|
||||
* @property isAllowedToSpend does spend allowed
|
||||
*/
|
||||
data class QuoteModel(
|
||||
val fromTokenAmount: SwapAmount,
|
||||
|
|
@ -16,5 +15,4 @@ data class QuoteModel(
|
|||
val fromTokenAddress: String,
|
||||
val toTokenAddress: String,
|
||||
val estimatedGas: Int,
|
||||
val isAllowedToSpend: Boolean = false,
|
||||
)
|
||||
|
|
@ -11,7 +11,7 @@ package com.tangem.feature.swap.domain.models
|
|||
data class SwapDataModel(
|
||||
val fromTokenAddress: String,
|
||||
val toTokenAddress: String,
|
||||
val toTokenAmount: String,
|
||||
val fromTokenAmount: String,
|
||||
val transaction: TransactionModel
|
||||
val toTokenAmount: SwapAmount,
|
||||
val fromTokenAmount: SwapAmount,
|
||||
val transaction: TransactionModel,
|
||||
)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed interface SwapState {
|
||||
|
||||
data class QuotesLoadedState(
|
||||
|
|
@ -11,8 +13,9 @@ sealed interface SwapState {
|
|||
val fromTokenFiatBalance: String,
|
||||
val toTokenWalletBalance: String,
|
||||
val toTokenFiatBalance: String,
|
||||
val estimatedGas: Int,
|
||||
val fee: String,
|
||||
val isAllowedToSpend: Boolean = false,
|
||||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
) : SwapState
|
||||
|
||||
data class SwapSuccess(
|
||||
|
|
@ -23,4 +26,21 @@ sealed interface SwapState {
|
|||
data class SwapError(
|
||||
val errorType: DataError,
|
||||
) : SwapState
|
||||
}
|
||||
|
||||
sealed class PermissionDataState {
|
||||
|
||||
data class PermissionReadyForRequest(
|
||||
val currency: String,
|
||||
val amount: String,
|
||||
val walletAddress: String,
|
||||
val spenderAddress: String,
|
||||
val fee: String,
|
||||
) : PermissionDataState()
|
||||
|
||||
object PermissionFailed : PermissionDataState()
|
||||
|
||||
object PermissionLoading : PermissionDataState()
|
||||
|
||||
object Empty : PermissionDataState()
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
package com.tangem.feature.swap.domain.models.cache
|
||||
|
||||
import com.tangem.feature.swap.domain.models.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.SwapDataModel
|
||||
|
||||
data class SwapDataHolder(
|
||||
val quoteModel: QuoteModel? = null,
|
||||
val swapModel: SwapDataModel? = null,
|
||||
val approveTxModel: ApproveModel? = null,
|
||||
val amountToSwap: SwapAmount? = null,
|
||||
val networkId: String? = null,
|
||||
val exchangeCurrencies: ExchangeCurrencies? = null,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.PermissionDataState
|
||||
import com.tangem.feature.swap.domain.models.SwapState
|
||||
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.models.ApprovePermissionButton
|
||||
import com.tangem.feature.swap.models.CancelPermissionButton
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
import com.tangem.feature.swap.models.SwapButton
|
||||
import com.tangem.feature.swap.models.SwapCardData
|
||||
import com.tangem.feature.swap.models.SwapPermissionStateHolder
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.SwapWarning
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
|
|
@ -75,6 +79,7 @@ class StateBuilder {
|
|||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: Currency,
|
||||
onSwapClick: () -> Unit,
|
||||
onGivePermissionClick: () -> Unit,
|
||||
): SwapStateHolder {
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
|
|
@ -102,7 +107,8 @@ class StateBuilder {
|
|||
} else {
|
||||
emptyList()
|
||||
},
|
||||
fee = FeeState.Loaded(quoteModel.estimatedGas.toString()),
|
||||
permissionState = convertPermissionState(quoteModel.permissionState, onGivePermissionClick),
|
||||
fee = FeeState.Loaded(quoteModel.fee),
|
||||
swapButton = SwapButton(
|
||||
enabled = quoteModel.isAllowedToSpend,
|
||||
loading = false,
|
||||
|
|
@ -110,4 +116,30 @@ class StateBuilder {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPermissionState(
|
||||
permissionDataState: PermissionDataState,
|
||||
onGivePermissionClick: () -> Unit,
|
||||
): SwapPermissionStateHolder? {
|
||||
return when (permissionDataState) {
|
||||
PermissionDataState.Empty -> null
|
||||
PermissionDataState.PermissionFailed -> null
|
||||
PermissionDataState.PermissionLoading -> null
|
||||
is PermissionDataState.PermissionReadyForRequest -> SwapPermissionStateHolder(
|
||||
currency = permissionDataState.currency,
|
||||
amount = permissionDataState.amount,
|
||||
walletAddress = permissionDataState.walletAddress,
|
||||
spenderAddress = permissionDataState.spenderAddress,
|
||||
fee = permissionDataState.fee,
|
||||
approveButton = ApprovePermissionButton(
|
||||
enabled = true,
|
||||
onClick = onGivePermissionClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,9 +98,13 @@ internal class SwapViewModel @Inject constructor(
|
|||
.onSuccess { swapState ->
|
||||
when (swapState) {
|
||||
is SwapState.QuotesLoadedState -> {
|
||||
uiState = stateBuilder.createQuotesLoadedState(uiState, swapState, fromToken) {
|
||||
onSwapClick(toToken, swapState)
|
||||
}
|
||||
uiState = stateBuilder.createQuotesLoadedState(
|
||||
uiStateHolder = uiState,
|
||||
quoteModel = swapState,
|
||||
fromToken = fromToken,
|
||||
onSwapClick = { onSwapClick(toToken, swapState) },
|
||||
onGivePermissionClick = { givePermissionsToSwap(fromToken) },
|
||||
)
|
||||
}
|
||||
is SwapState.SwapSuccess -> {
|
||||
//todo implement
|
||||
|
|
@ -116,10 +120,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
private fun onSwapClick(toToken: Currency, quoteModel: SwapState.QuotesLoadedState) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching {
|
||||
if (!quoteModel.isAllowedToSpend) {
|
||||
swapInteractor.givePermissionToSwap(toToken)
|
||||
} else {
|
||||
swapInteractor.onSwap()
|
||||
withContext(dispatchers.io) {
|
||||
if (!quoteModel.isAllowedToSpend) {
|
||||
swapInteractor.givePermissionToSwap(toToken)
|
||||
} else {
|
||||
swapInteractor.onSwap()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onSuccess {
|
||||
|
|
@ -137,6 +143,18 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun givePermissionsToSwap(tokenToApprove: Currency) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching {
|
||||
withContext(dispatchers.io) {
|
||||
swapInteractor.givePermissionToSwap(tokenToApprove)
|
||||
}
|
||||
}
|
||||
.onSuccess { }
|
||||
.onFailure { }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MIN_TOKENS_IN_LIST = 2
|
||||
private const val INITIAL_AMOUNT = "1"
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ interface TransactionManager {
|
|||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getNativeTokenDecimals(networkId: String): Int
|
||||
|
||||
fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal
|
||||
}
|
||||
|
|
@ -53,4 +53,10 @@ interface UserWalletManager {
|
|||
* Returns selected app currency
|
||||
*/
|
||||
fun getUserAppCurrency(): ProxyFiatCurrency
|
||||
|
||||
/**
|
||||
* @param networkId
|
||||
* @return currency name
|
||||
*/
|
||||
fun getCurrencyByNetworkId(networkId: String): String
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue