Updated on 2026-08-14
This commit is contained in:
parent
a4beb2cafc
commit
bd62089c12
39 changed files with 149 additions and 664 deletions
|
|
@ -37,6 +37,9 @@ dependencies {
|
|||
/** Tangem SDKs */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
||||
/** Others */
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.left
|
|||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
|
|
@ -13,12 +14,12 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.request.PairsRequestBody
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
|
||||
import com.tangem.datasource.api.express.models.response.SwapPair
|
||||
import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
|
||||
import com.tangem.datasource.api.oneinch.OneInchApi
|
||||
import com.tangem.datasource.api.oneinch.OneInchApiFactory
|
||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -34,20 +35,23 @@ import com.tangem.feature.swap.domain.models.domain.*
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo as NetworkLeastTokenInfo
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class SwapRepositoryImpl @Inject constructor(
|
||||
internal class DefaultSwapRepository @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val oneInchApiFactory: OneInchApiFactory,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val configManager: ConfigManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletsStateHolder: WalletsStateHolder,
|
||||
private val errorsDataConverter: ErrorsDataConverter,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
) : SwapRepository {
|
||||
|
||||
private val tokensConverter = TokensConverter()
|
||||
|
|
@ -56,6 +60,7 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
private val swapPairInfoConverter = SwapPairInfoConverter()
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
private val exchangeStatusConverter = ExchangeStatusConverter()
|
||||
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
||||
|
||||
override suspend fun getPairs(
|
||||
initialCurrency: LeastTokenInfo,
|
||||
|
|
@ -253,12 +258,6 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun addressForTrust(networkId: String): String {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
getOneInchApi(networkId).approveSpender().address
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getExchangeData(
|
||||
fromContractAddress: String,
|
||||
fromNetwork: String,
|
||||
|
|
@ -273,6 +272,7 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
): Either<DataError, SwapDataModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
try {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val response = tangemExpressApi.getExchangeData(
|
||||
fromContractAddress = fromContractAddress,
|
||||
fromNetwork = fromNetwork,
|
||||
|
|
@ -284,16 +284,36 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
providerId = providerId,
|
||||
rateType = rateType.name.lowercase(),
|
||||
toAddress = toAddress,
|
||||
requestId = requestId,
|
||||
).getOrThrow()
|
||||
expressDataConverter.convert(response).right()
|
||||
if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) {
|
||||
val txDetails = parseTxDetails(response.txDetailsJson)
|
||||
?: return@withContext DataError.UnknownError.left()
|
||||
if (txDetails.requestId != requestId) {
|
||||
return@withContext DataError.InvalidRequestIdError().left()
|
||||
}
|
||||
expressDataConverter.convert(
|
||||
ExchangeDataResponseWithTxDetails(
|
||||
dataResponse = response,
|
||||
txDetails = txDetails,
|
||||
),
|
||||
).right()
|
||||
} else {
|
||||
DataError.InvalidSignatureError().left()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
getDataError(ex).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTangemFee(): Double {
|
||||
return configManager.config.swapReferrerAccount?.fee?.toDoubleOrNull() ?: 0.0
|
||||
private fun parseTxDetails(txDetailsJson: String): TxDetails? {
|
||||
return try {
|
||||
txDetailsMoshiAdapter.fromJson(txDetailsJson)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "error parsing txDetailsJson")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllowance(
|
||||
|
|
@ -367,10 +387,6 @@ internal class SwapRepositoryImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getOneInchApi(networkId: String): OneInchApi {
|
||||
return oneInchApiFactory.getApi(networkId)
|
||||
}
|
||||
|
||||
override fun getNativeTokenForNetwork(networkId: String): CryptoCurrency {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
|
||||
|
|
@ -1,36 +1,42 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
|
||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.api.express.models.response.TxType
|
||||
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
|
||||
import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class ExpressDataConverter : Converter<ExchangeDataResponse, SwapDataModel> {
|
||||
internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetails, SwapDataModel> {
|
||||
|
||||
override fun convert(value: ExchangeDataResponse): SwapDataModel {
|
||||
override fun convert(value: ExchangeDataResponseWithTxDetails): SwapDataModel {
|
||||
val data = value.dataResponse
|
||||
return SwapDataModel(
|
||||
toTokenAmount = createFromAmountWithOffset(value.toAmount, value.toDecimals),
|
||||
transaction = convertTransaction(value),
|
||||
toTokenAmount = createFromAmountWithOffset(data.toAmount, data.toDecimals),
|
||||
transaction = convertTransaction(value.txDetails, data),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertTransaction(transactionDto: ExchangeDataResponse): ExpressTransactionModel {
|
||||
private fun convertTransaction(
|
||||
transactionDto: TxDetails,
|
||||
dataResponse: ExchangeDataResponse,
|
||||
): ExpressTransactionModel {
|
||||
return if (transactionDto.txType == TxType.SWAP) {
|
||||
ExpressTransactionModel.DEX(
|
||||
fromAmount = createFromAmountWithOffset(transactionDto.fromAmount, transactionDto.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(transactionDto.toAmount, transactionDto.toDecimals),
|
||||
txId = transactionDto.txId,
|
||||
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
txFrom = requireNotNull(transactionDto.txFrom),
|
||||
txData = requireNotNull(transactionDto.txData),
|
||||
)
|
||||
} else {
|
||||
ExpressTransactionModel.CEX(
|
||||
fromAmount = createFromAmountWithOffset(transactionDto.fromAmount, transactionDto.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(transactionDto.toAmount, transactionDto.toDecimals),
|
||||
txId = transactionDto.txId,
|
||||
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
externalTxId = requireNotNull(transactionDto.externalTxId),
|
||||
externalTxUrl = requireNotNull(transactionDto.externalTxUrl),
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@ package com.tangem.feature.swap.di
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.oneinch.OneInchApiFactory
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.feature.swap.SwapRepositoryImpl
|
||||
import com.tangem.feature.swap.converters.ErrorsDataConverter
|
||||
import com.tangem.feature.swap.DefaultSwapTransactionRepository
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.DefaultSwapRepository
|
||||
import com.tangem.feature.swap.converters.ErrorsDataConverter
|
||||
import com.tangem.feature.swap.domain.SwapTransactionRepository
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -31,22 +30,22 @@ internal class SwapDataModule {
|
|||
internal fun provideSwapRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
tangemExpressApi: TangemExpressApi,
|
||||
oneInchApiFactory: OneInchApiFactory,
|
||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
configManager: ConfigManager,
|
||||
dataSignature: DataSignatureVerifier,
|
||||
walletManagerFacade: WalletManagersFacade,
|
||||
walletsStateHolder: WalletsStateHolder,
|
||||
errorsDataConverter: ErrorsDataConverter,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): SwapRepository {
|
||||
return SwapRepositoryImpl(
|
||||
return DefaultSwapRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
oneInchApiFactory = oneInchApiFactory,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
configManager = configManager,
|
||||
walletManagersFacade = walletManagerFacade,
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
errorsDataConverter = errorsDataConverter,
|
||||
dataSignatureVerifier = dataSignature,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,19 +33,6 @@ interface SwapRepository {
|
|||
rateType: RateType,
|
||||
): Either<DataError, QuoteModel>
|
||||
|
||||
/**
|
||||
* Returns address of 1inch router that must be trusted
|
||||
*
|
||||
* @return address
|
||||
*/
|
||||
suspend fun addressForTrust(networkId: String): String
|
||||
|
||||
/**
|
||||
* Returns a tangem fee for swap in percents
|
||||
* Example: 0.35%
|
||||
*/
|
||||
fun getTangemFee(): Double
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getAllowance(
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ sealed class DataError {
|
|||
|
||||
data class UnknownErrorWithCode(override val code: Int) : DataError()
|
||||
|
||||
data class InvalidSignatureError(override val code: Int = 990) : DataError()
|
||||
|
||||
data class InvalidRequestIdError(override val code: Int = 991) : DataError()
|
||||
|
||||
object UnknownError : DataError() {
|
||||
override val code: Int = -1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ sealed interface SwapState {
|
|||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val txFee: TxFeeState,
|
||||
val tangemFee: Double,
|
||||
) : SwapState
|
||||
|
||||
data class EmptyAmountState(
|
||||
|
|
|
|||
|
|
@ -653,11 +653,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return repository.getNativeTokenForNetwork(networkId)
|
||||
}
|
||||
|
||||
@Deprecated("used in old swap mechanism")
|
||||
private fun getTangemFee(): Double {
|
||||
return repository.getTangemFee()
|
||||
}
|
||||
|
||||
private fun getTokenDecimals(token: CryptoCurrency): Int {
|
||||
return if (token is CryptoCurrency.Token) {
|
||||
token.decimals
|
||||
|
|
@ -1013,7 +1008,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
),
|
||||
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
|
||||
swapDataModel = swapData,
|
||||
tangemFee = getTangemFee(),
|
||||
txFee = txFeeState,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ internal class StateBuilder(
|
|||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReadOnly(
|
||||
headerResId = R.string.exchange_send_view_header
|
||||
headerResId = R.string.exchange_send_view_header,
|
||||
),
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue