Updated on 2026-08-14
This commit is contained in:
parent
87c43ab20f
commit
8261e72fec
25 changed files with 269 additions and 48 deletions
|
|
@ -31,6 +31,8 @@ dependencies {
|
|||
implementation(project(":core:utils"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0")
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ 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.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
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
|
||||
import com.tangem.feature.swap.domain.models.data.SwapDataModel
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
|
@ -64,16 +65,16 @@ class SwapRepositoryImpl @Inject constructor(
|
|||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
amount: String,
|
||||
fromAddress: String,
|
||||
fromWalletAddress: String,
|
||||
slippage: Int,
|
||||
) {
|
||||
): SwapDataModel {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
swapConverter.convert(
|
||||
oneInchApi.swap(
|
||||
fromTokenAddress = fromTokenAddress,
|
||||
toTokenAddress = toTokenAddress,
|
||||
amount = amount,
|
||||
fromAddress = fromAddress,
|
||||
fromAddress = fromWalletAddress,
|
||||
slippage = slippage,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse
|
||||
import com.tangem.feature.swap.domain.models.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.data.ApproveModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.oneinch.models.QuoteResponse
|
||||
import com.tangem.feature.swap.domain.models.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class QuotesConverter : Converter<QuoteResponse, QuoteModel> {
|
||||
class QuotesConverter @Inject constructor(): Converter<QuoteResponse, QuoteModel> {
|
||||
|
||||
override fun convert(value: QuoteResponse): QuoteModel {
|
||||
return QuoteModel(
|
||||
|
|
|
|||
|
|
@ -1,24 +1,32 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.oneinch.models.SwapResponse
|
||||
import com.tangem.feature.swap.domain.models.SwapTransactionModel
|
||||
import com.tangem.datasource.api.oneinch.models.TransactionDto
|
||||
import com.tangem.feature.swap.domain.models.data.SwapDataModel
|
||||
import com.tangem.feature.swap.domain.models.data.TransactionModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
class SwapConverter @Inject constructor() : Converter<SwapResponse, SwapTransactionModel> {
|
||||
class SwapConverter @Inject constructor() : Converter<SwapResponse, SwapDataModel> {
|
||||
|
||||
override fun convert(value: SwapResponse): SwapTransactionModel {
|
||||
return SwapTransactionModel(
|
||||
override fun convert(value: SwapResponse): SwapDataModel {
|
||||
return SwapDataModel(
|
||||
fromTokenAddress = value.fromToken.address,
|
||||
toTokenAddress = value.toToken.address,
|
||||
toTokenAmount = value.fromTokenAmount,
|
||||
fromTokenAmount = value.toTokenAmount,
|
||||
fromWalletAddress = value.transaction.fromAddress,
|
||||
toWalletAddress = value.transaction.toAddress,
|
||||
data = value.transaction.data,
|
||||
value = value.transaction.value,
|
||||
gasPrice = value.transaction.gasPrice,
|
||||
gas = value.transaction.gas,
|
||||
transaction = convertTransaction(value.transaction),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertTransaction(transactionDto: TransactionDto): TransactionModel {
|
||||
return TransactionModel(
|
||||
fromWalletAddress = transactionDto.fromAddress,
|
||||
toWalletAddress = transactionDto.toAddress,
|
||||
data = transactionDto.data,
|
||||
value = transactionDto.value,
|
||||
gasPrice = transactionDto.gasPrice,
|
||||
gas = transactionDto.gas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.CoinsResponse
|
||||
import com.tangem.feature.swap.domain.models.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ class TokensConverter @Inject constructor() : Converter<CoinsResponse.Coin, Curr
|
|||
networkId = network.networkId,
|
||||
contractAddress = network.contractAddress!!,
|
||||
decimalCount = network.decimalCount!!.intValueExact(),
|
||||
logoUrl = "",//todo add logo
|
||||
logoUrl = getSmallIconUrl(value.id),
|
||||
)
|
||||
} else {
|
||||
Currency.NativeToken(
|
||||
|
|
@ -25,8 +25,17 @@ class TokensConverter @Inject constructor() : Converter<CoinsResponse.Coin, Curr
|
|||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
networkId = network.networkId,
|
||||
logoUrl = "", //todo add logo
|
||||
logoUrl = getSmallIconUrl(value.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSmallIconUrl(coin: String): String {
|
||||
return "$DEFAULT_IMAGE_HOST$SMALL_ICON_PATH/$coin.png"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
private const val SMALL_ICON_PATH = "small"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.datasource.api.oneinch.OneInchApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.di.qualifiers.OneInchEthereum
|
||||
import com.tangem.feature.swap.SwapRepositoryImpl
|
||||
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.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class SwapDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
@OneInchEthereum oneInchApi: OneInchApi,
|
||||
tokensConverter: TokensConverter,
|
||||
quotesConverter: QuotesConverter,
|
||||
swapConverter: SwapConverter,
|
||||
approveConverter: ApproveConverter,
|
||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
): SwapRepository {
|
||||
return SwapRepositoryImpl(
|
||||
tangemTechApi = tangemTechApi,
|
||||
oneInchApi = oneInchApi,
|
||||
tokensConverter = tokensConverter,
|
||||
quotesConverter = quotesConverter,
|
||||
swapConverter = swapConverter,
|
||||
approveConverter = approveConverter,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,20 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.SwapResultModel
|
||||
|
||||
interface SwapInteractor {
|
||||
|
||||
suspend fun getTokensToSwap(networkId: String): List<Currency>
|
||||
|
||||
suspend fun getTokenBalance(): String
|
||||
|
||||
suspend fun findBestQuote(
|
||||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
amount: String,
|
||||
): QuoteModel
|
||||
|
||||
suspend fun onSwap(): SwapResultModel
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.SwapDataHolder
|
||||
import com.tangem.feature.swap.domain.models.SwapResultModel
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.data.TransactionModel
|
||||
|
||||
//todo add @Inject after merge referral and add HiltCore
|
||||
class SwapInteractorImpl(
|
||||
private val repository: SwapRepository,
|
||||
) : SwapInteractor {
|
||||
|
||||
private var lastDataForSwap: SwapDataHolder? = null
|
||||
|
||||
override suspend fun getTokensToSwap(networkId: String): List<Currency> {
|
||||
return repository.getExchangeableTokens(networkId)
|
||||
}
|
||||
|
||||
override suspend fun getTokenBalance(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel {
|
||||
repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let {
|
||||
lastDataForSwap = SwapDataHolder(it, amount)
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onSwap(): SwapResultModel {
|
||||
val dataForSwap = lastDataForSwap
|
||||
if (dataForSwap != null) {
|
||||
val swapData = repository.prepareSwapTransaction(
|
||||
fromTokenAddress = dataForSwap.quoteModel.fromTokenAmount,
|
||||
toTokenAddress = dataForSwap.quoteModel.toTokenAmount,
|
||||
amount = dataForSwap.amount,
|
||||
slippage = DEFAULT_SLIPPAGE,
|
||||
fromWalletAddress = getWalletAddress(),
|
||||
)
|
||||
signTransactionData(swapData.transaction) //todo implement
|
||||
return SwapResultModel.SwapSuccess(
|
||||
dataForSwap.quoteModel.fromTokenAmount,
|
||||
dataForSwap.quoteModel.toTokenAmount,
|
||||
)
|
||||
}
|
||||
return SwapResultModel.SwapError(
|
||||
0,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
//todo implement after merge referral
|
||||
private fun getWalletAddress(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
//todo implement after merge referral
|
||||
private fun signTransactionData(transaction: TransactionModel) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_SLIPPAGE = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
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.data.ApproveModel
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
import com.tangem.feature.swap.domain.models.data.SwapDataModel
|
||||
|
||||
interface SwapRepository {
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ interface SwapRepository {
|
|||
fromTokenAddress: String,
|
||||
toTokenAddress: String,
|
||||
amount: String,
|
||||
fromAddress: String,
|
||||
fromWalletAddress: String,
|
||||
slippage: Int,
|
||||
)
|
||||
): SwapDataModel
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
import com.tangem.feature.swap.domain.models.data.QuoteModel
|
||||
|
||||
data class SwapDataHolder(
|
||||
val quoteModel: QuoteModel,
|
||||
val amount: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
sealed interface SwapResultModel {
|
||||
|
||||
data class SwapSuccess(
|
||||
val fromTokenAmount: String,
|
||||
val toTokenAmount: String,
|
||||
) : SwapResultModel
|
||||
|
||||
data class SwapError(
|
||||
val errorCode: Int,
|
||||
val errorMessage: String,
|
||||
) : SwapResultModel
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
/**
|
||||
* Approve model
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
sealed class Currency(
|
||||
open val id: String,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
data class QuoteModel(
|
||||
val fromTokenAmount: String,
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
/**
|
||||
* Swap transaction model
|
||||
*
|
||||
* @property fromTokenAddress token from which want to convert
|
||||
* @property toTokenAddress token to want to convert
|
||||
* @property toTokenAmount amount "target" token
|
||||
* @property fromTokenAmount amount "initial" token
|
||||
*/
|
||||
data class SwapDataModel(
|
||||
val fromTokenAddress: String,
|
||||
val toTokenAddress: String,
|
||||
val toTokenAmount: String,
|
||||
val fromTokenAmount: String,
|
||||
val transaction: TransactionModel
|
||||
)
|
||||
|
|
@ -1,25 +1,16 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
package com.tangem.feature.swap.domain.models.data
|
||||
|
||||
/**
|
||||
* Swap transaction model
|
||||
* Transaction model
|
||||
*
|
||||
* @property fromTokenAddress token from which want to convert
|
||||
* @property toTokenAddress token to want to convert
|
||||
* @property toTokenAmount amount "target" token
|
||||
* @property fromTokenAmount amount "initial" token
|
||||
* @property fromWalletAddress wallet from address (transactions will be sent from this address)
|
||||
* @property toWalletAddress transactions will be sent to our(1inch) contract address
|
||||
* @property data The encoded data to call the approve method on the swapped token contract
|
||||
* @property value Native token value in WEI (for approve is always 0)
|
||||
* @property gasPrice maximum amount of gas for a swap default: 11500000; max: 11500000
|
||||
* @property gas estimated amount of the gas limit, increase this value by 25%
|
||||
* @constructor Create empty Swap transaction model
|
||||
*/
|
||||
data class SwapTransactionModel(
|
||||
val fromTokenAddress: String,
|
||||
val toTokenAddress: String,
|
||||
val toTokenAmount: String,
|
||||
val fromTokenAmount: String,
|
||||
data class TransactionModel(
|
||||
val fromWalletAddress: String,
|
||||
val toWalletAddress: String,
|
||||
val data: String,
|
||||
|
|
@ -49,6 +49,7 @@ dependencies {
|
|||
|
||||
/** Domain */
|
||||
implementation(project(":features:swap:domain"))
|
||||
implementation(project(":features:swap:data"))//todo remove, only for test
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.composeShimmer)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.feature.swap.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.feature.swap.domain.SwapInteractorImpl
|
||||
import com.tangem.feature.swap.domain.SwapRepository
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SwapFragment : Fragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var swapRepository: SwapRepository
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
//test
|
||||
val swapInteractor = SwapInteractorImpl(swapRepository)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Log.d("test", swapInteractor.getTokensToSwap("ethereum").toString())
|
||||
}
|
||||
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue