From b87f415e2994aa2fa28b4284aa16248b360b82f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 21:16:53 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../datasource/api/oneinch/BaseResponse.kt | 8 +++++ .../datasource/api/oneinch/OneInchApi.kt | 5 +-- .../api/oneinch/OneInchErrorsHandler.kt | 34 +++++++++++++++++++ .../com/tangem/datasource/di/NetworkModule.kt | 31 +++++++++-------- features/swap/data/build.gradle.kts | 3 ++ .../tangem/feature/swap/SwapRepositoryImpl.kt | 16 ++++++--- .../swap/converters/QuotesConverter.kt | 27 +++++++++++---- .../feature/swap/converters/SwapConverter.kt | 31 ++++++++++++----- .../tangem/feature/swap/di/SwapDataModule.kt | 3 ++ .../feature/swap/domain/SwapInteractorImpl.kt | 25 +++++++++----- .../feature/swap/domain/SwapRepository.kt | 5 +-- .../domain/models/AggregatedSwapDataModel.kt | 15 ++++++++ .../swap/domain/models/data/DataError.kt | 18 ++++++++++ 13 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt new file mode 100644 index 0000000000..866c26d858 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/BaseResponse.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.api.oneinch + +import com.tangem.datasource.api.oneinch.models.SwapErrorDto + +data class BaseOneInchResponse( + val body: T?, + val errorDto: SwapErrorDto?, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt index e230166425..613d5d1000 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -8,6 +8,7 @@ import com.tangem.datasource.api.oneinch.models.QuoteResponse import com.tangem.datasource.api.oneinch.models.StatusResponse import com.tangem.datasource.api.oneinch.models.SwapResponse import com.tangem.datasource.api.oneinch.models.TokensResponse +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query @@ -127,7 +128,7 @@ interface OneInchApi { @Query("mainRouteParts") mainRouteParts: String? = null, @Query("parts") parts: String? = null, @Query("gasPrice") gasPrice: String? = null, - ): QuoteResponse + ): Response /** * Generate data for calling the 1inch router for exchange @@ -197,6 +198,6 @@ interface OneInchApi { @Query("complexityLevel") complexityLevel: String? = null, @Query("gasLimit") gasLimit: String? = null, @Query("gasPrice") gasPrice: String? = null, - ): SwapResponse + ): Response //endregion Swap } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt new file mode 100644 index 0000000000..72e9cd5342 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchErrorsHandler.kt @@ -0,0 +1,34 @@ +package com.tangem.datasource.api.oneinch + +import com.squareup.moshi.Moshi +import com.tangem.datasource.api.oneinch.models.SwapErrorDto +import retrofit2.HttpException +import retrofit2.Response +import javax.inject.Inject + +class OneInchErrorsHandler @Inject constructor(private val moshi: Moshi) { + + fun handleOneInchResponse(response: Response): BaseOneInchResponse { + val body = response.body() + return if (response.isSuccessful) { + return BaseOneInchResponse(body, null) + } else { + when (response.code()) { + HTTP_CODE_400 -> { + if (body != null) { + BaseOneInchResponse(null, moshi.adapter(SwapErrorDto::class.java).fromJson(body.toString())) + } else { + throw HttpException(response) + } + } + else -> { + throw HttpException(response) + } + } + } + } + + companion object { + private const val HTTP_CODE_400 = 400 + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 94977976b0..a79330cfcf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -15,7 +15,6 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Singleton @@ -28,10 +27,12 @@ class NetworkModule { @Singleton fun provideTangemTechApi( okHttpClient: OkHttpClient, - converter: Converter.Factory, + moshi: Moshi, ): TangemTechApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -41,9 +42,11 @@ class NetworkModule { @Provides @Singleton @OneInchEthereum - fun provideOneInchEthereumApi(converter: Converter.Factory): OneInchApi { + fun provideOneInchEthereumApi(moshi: Moshi): OneInchApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(ONE_INCH_ETHER_BASE_URL) .client( OkHttpClient.Builder() @@ -58,9 +61,11 @@ class NetworkModule { @Provides @Singleton - fun provideReferralApi(okHttpClient: OkHttpClient, converter: Converter.Factory): ReferralApi { + fun provideReferralApi(okHttpClient: OkHttpClient, moshi: Moshi): ReferralApi { return Retrofit.Builder() - .addConverterFactory(converter) + .addConverterFactory( + MoshiConverterFactory.create(moshi), + ) .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() @@ -80,13 +85,11 @@ class NetworkModule { @Provides @Singleton - fun provideMoshiConverter(): Converter.Factory { - return MoshiConverterFactory.create( - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .add(BigDecimalAdapter()) - .build(), - ) + fun provideMoshi(): Moshi { + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(BigDecimalAdapter()) + .build() } private companion object { diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 0d610522bf..9f2c7c0c4d 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -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) 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 7ea4007b3f..ca376c5d99 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 @@ -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 { 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 { 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, + ) } } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt index 058a6a6159..fe37564101 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -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 { +class QuotesConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { - override fun convert(value: QuoteResponse): QuoteModel { - return QuoteModel( - fromTokenAmount = value.fromTokenAmount, - toTokenAmount = value.toTokenAmount, - estimatedGas = value.estimatedGas, - ) + override fun convert(value: BaseOneInchResponse): AggregatedSwapDataModel { + 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), + ) + } } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt index 87dec263da..1c2fb0e2c2 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -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 { +class SwapConverter @Inject constructor() : Converter, AggregatedSwapDataModel> { - 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): AggregatedSwapDataModel { + 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 { 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 5e4ed69628..0fdb016f7b 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 @@ -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, ) } 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 87e05aa527..942a6d256d 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 @@ -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, 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 cb2a2bf07a..4c8254c809 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 @@ -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 /** * Returns address of 1inch router that must be trusted @@ -49,5 +50,5 @@ interface SwapRepository { amount: String, fromWalletAddress: String, slippage: Int, - ): SwapDataModel + ): AggregatedSwapDataModel } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt new file mode 100644 index 0000000000..2554398d3c --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/AggregatedSwapDataModel.kt @@ -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( + val dataModel: T?, + val error: DataError = DataError.NO_ERROR, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt new file mode 100644 index 0000000000..6498ac07e0 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/data/DataError.kt @@ -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" \ No newline at end of file