Updated on 2026-08-14
This commit is contained in:
parent
0ecd5e8da1
commit
b87f415e29
13 changed files with 175 additions and 46 deletions
|
|
@ -45,6 +45,9 @@ dependencies {
|
|||
implementation(project(":core:utils"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
|
||||
/** Network */
|
||||
implementation(Library.retrofit)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import com.tangem.datasource.api.oneinch.OneInchApi
|
||||
import com.tangem.datasource.api.oneinch.OneInchErrorsHandler
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.feature.swap.converters.ApproveConverter
|
||||
import com.tangem.feature.swap.converters.QuotesConverter
|
||||
import com.tangem.feature.swap.converters.SwapConverter
|
||||
import com.tangem.feature.swap.converters.TokensConverter
|
||||
import com.tangem.feature.swap.domain.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
|
|
@ -22,6 +24,7 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
private val quotesConverter: QuotesConverter,
|
||||
private val swapConverter: SwapConverter,
|
||||
private val approveConverter: ApproveConverter,
|
||||
private val oneInchErrorsHandler: OneInchErrorsHandler,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
) : SwapRepository {
|
||||
|
||||
|
|
@ -31,15 +34,17 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel {
|
||||
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String):
|
||||
AggregatedSwapDataModel<QuoteModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
quotesConverter.convert(
|
||||
val quoteResponse = oneInchErrorsHandler.handleOneInchResponse(
|
||||
oneInchApi.quote(
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount,
|
||||
),
|
||||
)
|
||||
quotesConverter.convert(quoteResponse)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +72,9 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
amount: String,
|
||||
fromWalletAddress: String,
|
||||
slippage: Int,
|
||||
): SwapDataModel {
|
||||
): AggregatedSwapDataModel<SwapDataModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
swapConverter.convert(
|
||||
val swapResponse = oneInchErrorsHandler.handleOneInchResponse(
|
||||
oneInchApi.swap(
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
|
|
@ -78,6 +83,9 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
slippage = slippage,
|
||||
),
|
||||
)
|
||||
swapConverter.convert(
|
||||
swapResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,30 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.oneinch.BaseOneInchResponse
|
||||
import com.tangem.datasource.api.oneinch.models.QuoteResponse
|
||||
import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.data.mapErrors
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class QuotesConverter @Inject constructor(): Converter<QuoteResponse, QuoteModel> {
|
||||
class QuotesConverter @Inject constructor() : Converter<BaseOneInchResponse<QuoteResponse>, AggregatedSwapDataModel<QuoteModel>> {
|
||||
|
||||
override fun convert(value: QuoteResponse): QuoteModel {
|
||||
return QuoteModel(
|
||||
fromTokenAmount = value.fromTokenAmount,
|
||||
toTokenAmount = value.toTokenAmount,
|
||||
estimatedGas = value.estimatedGas,
|
||||
)
|
||||
override fun convert(value: BaseOneInchResponse<QuoteResponse>): AggregatedSwapDataModel<QuoteModel> {
|
||||
val body = value.body
|
||||
return if (body != null) {
|
||||
AggregatedSwapDataModel(
|
||||
QuoteModel(
|
||||
fromTokenAmount = body.fromTokenAmount,
|
||||
toTokenAmount = body.toTokenAmount,
|
||||
estimatedGas = body.estimatedGas,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
AggregatedSwapDataModel(
|
||||
null,
|
||||
mapErrors(value.errorDto?.description),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,35 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.oneinch.BaseOneInchResponse
|
||||
import com.tangem.datasource.api.oneinch.models.SwapResponse
|
||||
import com.tangem.datasource.api.oneinch.models.TransactionDto
|
||||
import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.TransactionModel
|
||||
import com.tangem.feature.swap.domain.models.data.mapErrors
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class SwapConverter @Inject constructor() : Converter<SwapResponse, SwapDataModel> {
|
||||
class SwapConverter @Inject constructor() : Converter<BaseOneInchResponse<SwapResponse>, AggregatedSwapDataModel<SwapDataModel>> {
|
||||
|
||||
override fun convert(value: SwapResponse): SwapDataModel {
|
||||
return SwapDataModel(
|
||||
fromTokenAddress = value.fromToken.address,
|
||||
toTokenAddress = value.toToken.address,
|
||||
toTokenAmount = value.fromTokenAmount,
|
||||
fromTokenAmount = value.toTokenAmount,
|
||||
transaction = convertTransaction(value.transaction),
|
||||
)
|
||||
override fun convert(value: BaseOneInchResponse<SwapResponse>): AggregatedSwapDataModel<SwapDataModel> {
|
||||
val body = value.body
|
||||
return if (body != null) {
|
||||
AggregatedSwapDataModel(
|
||||
SwapDataModel(
|
||||
fromTokenAddress = body.fromToken.address,
|
||||
toTokenAddress = body.toToken.address,
|
||||
toTokenAmount = body.fromTokenAmount,
|
||||
fromTokenAmount = body.toTokenAmount,
|
||||
transaction = convertTransaction(body.transaction),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
AggregatedSwapDataModel(
|
||||
null,
|
||||
mapErrors(value.errorDto?.description),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertTransaction(transactionDto: TransactionDto): TransactionModel {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.datasource.api.oneinch.OneInchApi
|
||||
import com.tangem.datasource.api.oneinch.OneInchErrorsHandler
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.di.qualifiers.OneInchEthereum
|
||||
import com.tangem.feature.swap.SwapRepositoryImpl
|
||||
|
|
@ -29,6 +30,7 @@ class SwapDataModule {
|
|||
quotesConverter: QuotesConverter,
|
||||
swapConverter: SwapConverter,
|
||||
approveConverter: ApproveConverter,
|
||||
oneInchErrorsHandler: OneInchErrorsHandler,
|
||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
): SwapRepository {
|
||||
return SwapRepositoryImpl(
|
||||
|
|
@ -38,6 +40,7 @@ class SwapDataModule {
|
|||
quotesConverter = quotesConverter,
|
||||
swapConverter = swapConverter,
|
||||
approveConverter = approveConverter,
|
||||
oneInchErrorsHandler = oneInchErrorsHandler,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) != "0"
|
||||
}
|
||||
repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes ->
|
||||
cache.cacheSwapParams(quotes.copy(isAllowedToSpend = isAllowedToSpend), amount)
|
||||
return quotes
|
||||
val quoteDataModel = quotes.dataModel
|
||||
if (quoteDataModel != null) {
|
||||
cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount)
|
||||
return quoteDataModel
|
||||
} else {
|
||||
error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,12 +57,16 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = amountToSwap,
|
||||
slippage = DEFAULT_SLIPPAGE,
|
||||
fromWalletAddress = getWalletAddress(networkId),
|
||||
)
|
||||
signTransactionData(swapData.transaction) //todo implement
|
||||
return SwapResultModel.SwapSuccess(
|
||||
quoteModel.fromTokenAmount,
|
||||
quoteModel.toTokenAmount,
|
||||
)
|
||||
).dataModel
|
||||
if (swapData != null) {
|
||||
signTransactionData(swapData.transaction) //todo implement
|
||||
return SwapResultModel.SwapSuccess(
|
||||
quoteModel.fromTokenAmount,
|
||||
quoteModel.toTokenAmount,
|
||||
)
|
||||
} else {
|
||||
error("") //todo handle error in domain layer(task [REDACTED_TASK_KEY])
|
||||
}
|
||||
}
|
||||
return SwapResultModel.SwapError(
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.AggregatedSwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
|
|
@ -13,7 +14,7 @@ interface SwapRepository {
|
|||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
amount: String,
|
||||
): QuoteModel
|
||||
): AggregatedSwapDataModel<QuoteModel>
|
||||
|
||||
/**
|
||||
* Returns address of 1inch router that must be trusted
|
||||
|
|
@ -49,5 +50,5 @@ interface SwapRepository {
|
|||
amount: String,
|
||||
fromWalletAddress: String,
|
||||
slippage: Int,
|
||||
): SwapDataModel
|
||||
): AggregatedSwapDataModel<SwapDataModel>
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.DataError
|
||||
|
||||
/**
|
||||
* Model that aggregate data model from repository return with error [DataError] if it exists
|
||||
*
|
||||
* @param T model type
|
||||
* @property dataModel
|
||||
* @property error possible from repository [DataError]
|
||||
*/
|
||||
data class AggregatedSwapDataModel<T>(
|
||||
val dataModel: T?,
|
||||
val error: DataError = DataError.NO_ERROR,
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
enum class DataError {
|
||||
NO_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
INSUFFICIENT_LIQUIDITY
|
||||
}
|
||||
|
||||
fun mapErrors(error: String?): DataError {
|
||||
return if (error == null) {
|
||||
DataError.UNKNOWN_ERROR
|
||||
} else when (error) {
|
||||
INSUFFICIENT_LIQUIDITY_ERROR -> DataError.INSUFFICIENT_LIQUIDITY
|
||||
else -> DataError.UNKNOWN_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
private const val INSUFFICIENT_LIQUIDITY_ERROR = "insufficient liquidity"
|
||||
Loading…
Add table
Add a link
Reference in a new issue