Updated on 2026-08-14

This commit is contained in:
Tangem 2022-12-22 16:25:58 +03:00
parent 9cf90df510
commit 1c5519a702
19 changed files with 327 additions and 83 deletions

View file

@ -5,6 +5,7 @@ import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.NativeToken
import com.tangem.lib.crypto.models.NonNativeToken
import com.tangem.utils.converter.Converter
import javax.inject.Inject
class TokensConverter @Inject constructor() : Converter<TokenData, Currency> {

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.api.oneinch.OneInchApi
import com.tangem.datasource.api.oneinch.OneInchErrorsHandler
import com.tangem.datasource.api.oneinch.errors.OneIncResponseException
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.OneInchApiFactory
import com.tangem.feature.swap.converters.ApproveConverter
import com.tangem.feature.swap.converters.QuotesConverter
import com.tangem.feature.swap.converters.SwapConverter
@ -21,7 +22,7 @@ import javax.inject.Inject
internal class SwapRepositoryImpl @Inject constructor(
private val tangemTechApi: TangemTechApi,
private val oneInchApi: OneInchApi,
private val oneInchApiFactory: OneInchApiFactory,
private val tokensConverter: TokensConverter,
private val quotesConverter: QuotesConverter,
private val swapConverter: SwapConverter,
@ -36,12 +37,16 @@ internal class SwapRepositoryImpl @Inject constructor(
}
}
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String):
override suspend fun findBestQuote(
networkId: String, fromTokenAddress: String, toTokenAddress: String,
amount:
String,
):
AggregatedSwapDataModel<QuoteModel> {
return withContext(coroutineDispatcher.io) {
try {
val response = oneInchErrorsHandler.handleOneInchResponse(
oneInchApi.quote(
getOneInchApi(networkId).quote(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
@ -54,24 +59,24 @@ internal class SwapRepositoryImpl @Inject constructor(
}
}
override suspend fun addressForTrust(): String {
override suspend fun addressForTrust(networkId: String): String {
return withContext(coroutineDispatcher.io) {
oneInchApi.approveSpender().address
getOneInchApi(networkId).approveSpender().address
}
}
override suspend fun dataToApprove(tokenAddress: String, amount: String?): ApproveModel {
override suspend fun dataToApprove(networkId: String, tokenAddress: String, amount: String?): ApproveModel {
return withContext(coroutineDispatcher.io) {
approveConverter.convert(oneInchApi.approveTransaction(tokenAddress, amount))
approveConverter.convert(getOneInchApi(networkId).approveTransaction(tokenAddress, amount))
}
}
override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String):
override suspend fun checkTokensSpendAllowance(networkId: String, tokenAddress: String, walletAddress: String):
AggregatedSwapDataModel<String> {
return withContext(coroutineDispatcher.io) {
try {
val response = oneInchErrorsHandler.handleOneInchResponse(
oneInchApi.approveAllowance(tokenAddress, walletAddress)
getOneInchApi(networkId).approveAllowance(tokenAddress, walletAddress),
)
AggregatedSwapDataModel(response.allowance)
} catch (ex: OneIncResponseException) {
@ -81,6 +86,7 @@ internal class SwapRepositoryImpl @Inject constructor(
}
override suspend fun prepareSwapTransaction(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
@ -90,7 +96,7 @@ internal class SwapRepositoryImpl @Inject constructor(
return withContext(coroutineDispatcher.io) {
try {
val swapResponse = oneInchErrorsHandler.handleOneInchResponse(
oneInchApi.swap(
getOneInchApi(networkId).swap(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
@ -104,4 +110,8 @@ internal class SwapRepositoryImpl @Inject constructor(
}
}
}
private fun getOneInchApi(networkId: String): OneInchApi {
return oneInchApiFactory.getApi(networkId)
}
}

View file

@ -1,9 +1,8 @@
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.datasource.di.OneInchApiFactory
import com.tangem.feature.swap.SwapRepositoryImpl
import com.tangem.feature.swap.converters.ApproveConverter
import com.tangem.feature.swap.converters.QuotesConverter
@ -25,7 +24,7 @@ class SwapDataModule {
@Singleton
fun provideSwapRepository(
tangemTechApi: TangemTechApi,
@OneInchEthereum oneInchApi: OneInchApi,
oneInchApiFactory: OneInchApiFactory,
tokensConverter: TokensConverter,
quotesConverter: QuotesConverter,
swapConverter: SwapConverter,
@ -35,7 +34,7 @@ class SwapDataModule {
): SwapRepository {
return SwapRepositoryImpl(
tangemTechApi = tangemTechApi,
oneInchApi = oneInchApi,
oneInchApiFactory = oneInchApiFactory,
tokensConverter = tokensConverter,
quotesConverter = quotesConverter,
swapConverter = swapConverter,

View file

@ -5,10 +5,13 @@ import com.tangem.feature.swap.domain.models.data.Currency
import com.tangem.feature.swap.domain.models.data.DataError
import com.tangem.feature.swap.domain.models.data.SwapState
import com.tangem.feature.swap.domain.models.data.TransactionModel
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import java.math.BigDecimal
import javax.inject.Inject
internal class SwapInteractorImpl @Inject constructor(
private val transactionManager: TransactionManager,
private val userWalletManager: UserWalletManager,
private val repository: SwapRepository,
private val cache: SwapDataCache,
@ -29,21 +32,40 @@ internal class SwapInteractorImpl @Inject constructor(
}
override suspend fun givePermissionToSwap(tokenAddress: String) {
val oneInchAddress = repository.addressForTrust()
val transactionData = repository.dataToApprove(tokenAddress)
//todo sign transaction (next tasks)
cache.getNetworkId()?.let { networkId ->
val oneInchAddress = repository.addressForTrust(networkId)
val transactionData = repository.dataToApprove(networkId, tokenAddress)
//todo for test
// transactionManager.sendTransaction(
// networkId = networkId,
// amountToSend = BigDecimal.ZERO,
// currencyToSend = ,
// feeAmount = BigDecimal.ZERO,
// destinationAddress = oneInchAddress,
// dataToSign = transactionData.data
// )
}
}
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): SwapState {
val networkId = cache.getNetworkId()
val isAllowedToSpend = if (networkId.isNullOrEmpty()) {
false
error("no networkId found, please call getTokensToSwap first")
} else {
val allowance =
repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId))
repository.checkTokensSpendAllowance(
networkId = networkId,
tokenAddress = fromTokenAddress,
walletAddress = userWalletManager.getWalletAddress(networkId),
)
allowance.error == DataError.NO_ERROR && allowance.dataModel != "0"
}
repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes ->
repository.findBestQuote(
networkId = networkId,
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
).let { quotes ->
val quoteDataModel = quotes.dataModel
if (quoteDataModel != null) {
cache.cacheSwapParams(quoteDataModel.copy(isAllowedToSpend = isAllowedToSpend), amount)
@ -60,6 +82,7 @@ internal class SwapInteractorImpl @Inject constructor(
val networkId = cache.getNetworkId()
if (quoteModel != null && amountToSwap != null && networkId != null) {
repository.prepareSwapTransaction(
networkId = networkId,
fromTokenAddress = quoteModel.fromTokenAmount,
toTokenAddress = quoteModel.toTokenAmount,
amount = amountToSwap,

View file

@ -11,6 +11,7 @@ interface SwapRepository {
suspend fun getExchangeableTokens(networkId: String): List<Currency>
suspend fun findBestQuote(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
@ -21,7 +22,7 @@ interface SwapRepository {
*
* @return address
*/
suspend fun addressForTrust(): String
suspend fun addressForTrust(networkId: String): String
/**
* Generate "data" for calling contract in order to allow 1inch spend funds
@ -30,6 +31,7 @@ interface SwapRepository {
* @param amount number of tokens is allowed. By default infinite
*/
suspend fun dataToApprove(
networkId: String,
tokenAddress: String,
amount: String? = null,
): ApproveModel
@ -42,9 +44,14 @@ interface SwapRepository {
*
* @return amount of tokens allowed to spend
*/
suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): AggregatedSwapDataModel<String>
suspend fun checkTokensSpendAllowance(
networkId: String,
tokenAddress: String,
walletAddress: String,
): AggregatedSwapDataModel<String>
suspend fun prepareSwapTransaction(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,

View file

@ -4,6 +4,7 @@ import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.SwapInteractorImpl
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import dagger.Module
import dagger.Provides
@ -18,12 +19,14 @@ class SwapDomainModule {
@Provides
@Singleton
fun provideSwapInteractor(
referralRepository: SwapRepository,
swapRepository: SwapRepository,
userWalletManager: UserWalletManager,
transactionManager: TransactionManager,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
userWalletManager = userWalletManager,
repository = referralRepository,
repository = swapRepository,
cache = SwapDataCacheImpl(),
)
}