Updated on 2026-08-14

This commit is contained in:
Tangem 2022-12-12 16:26:33 +03:00
parent 8261e72fec
commit 361ac9508b
21 changed files with 210 additions and 82 deletions

View file

@ -7,7 +7,6 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
@ -89,7 +88,7 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
fun FragmentActivity.addOnBackPressedDispatcher(
isEnabled: Boolean = true,
onBackPressed: VoidCallback
onBackPressed: VoidCallback,
): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) {
override fun handleOnBackPressed() {
onBackPressed()
@ -106,7 +105,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> WalletFragment()
AppScreen.Send -> SendFragment()
AppScreen.Details -> SwapFragment()//DetailsFragment()
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()
AppScreen.AppSettings -> AppSettingsFragment()

View file

@ -84,8 +84,8 @@ class UserWalletManagerImpl(
mainStore.dispatch(action)
}
override fun getWalletAddress(currency: Currency): String {
val blockchain = Blockchain.fromNetworkId(currency.networkId)
override fun getWalletAddress(networkId: String): String {
val blockchain = Blockchain.fromNetworkId(networkId)
val card = appStateHolder.getActualCard()
if (blockchain != null && card != null) {
val blockchainNetwork = BlockchainNetwork(blockchain, card)

View file

@ -63,7 +63,7 @@ dependencies {
implementation(Library.okHttp)
implementation(Library.okHttpLogging)
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0")
// kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0")
/** Time */
implementation(Library.jodatime)

View file

@ -114,6 +114,7 @@ interface OneInchApi {
*
* @return [QuoteResponse]
*/
@GET("quote")
suspend fun quote(
@Query("fromTokenAddress") fromTokenAddress: String,
@Query("toTokenAddress") toTokenAddress: String,
@ -174,6 +175,7 @@ interface OneInchApi {
*
* @return [SwapResponse]
*/
@GET("swap")
suspend fun swap(
@Query("fromTokenAddress") fromTokenAddress: String,
@Query("toTokenAddress") toTokenAddress: String,

View file

@ -15,8 +15,7 @@ class TangemTechService(
private val logIsEnabled: Boolean = false,
) {
private val
headerInterceptors = mutableListOf<AddHeaderInterceptor>(
private val headerInterceptors = mutableListOf<AddHeaderInterceptor>(
CacheControlHttpInterceptor(cacheMaxAge),
)

View file

@ -15,6 +15,7 @@ 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
@ -25,16 +26,12 @@ class NetworkModule {
@Provides
@Singleton
fun provideTangemTechApi(okHttpClient: OkHttpClient): TangemTechApi {
fun provideTangemTechApi(
okHttpClient: OkHttpClient,
converter: Converter.Factory,
): TangemTechApi {
return Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(BigDecimalAdapter())
.build(),
),
)
.addConverterFactory(converter)
.baseUrl(DEV_TANGEM_TECH_BASE_URL)
.client(okHttpClient)
.build()
@ -44,28 +41,26 @@ class NetworkModule {
@Provides
@Singleton
@OneInchEthereum
fun provideOneInchEthereumApi(): OneInchApi {
fun provideOneInchEthereumApi(converter: Converter.Factory): OneInchApi {
return Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(),
)
.addConverterFactory(converter)
.baseUrl(ONE_INCH_ETHER_BASE_URL)
.client(OkHttpClient())
.client(
OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY),
)
.build(),
)
.build()
.create(OneInchApi::class.java)
}
@Provides
@Singleton
fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi {
fun provideReferralApi(okHttpClient: OkHttpClient, converter: Converter.Factory): ReferralApi {
return Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(
Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build(),
),
)
.addConverterFactory(converter)
.baseUrl(DEV_TANGEM_TECH_BASE_URL)
.client(okHttpClient)
.build()
@ -83,9 +78,20 @@ class NetworkModule {
.build()
}
@Provides
@Singleton
fun provideMoshiConverter(): Converter.Factory {
return MoshiConverterFactory.create(
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(BigDecimalAdapter())
.build(),
)
}
private companion object {
const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/"
const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/"
const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/swagger/ethereum-json/"
const val ONE_INCH_ETHER_BASE_URL = "https://api.1inch.io/v5.0/1/"
}
}

View file

@ -7,8 +7,9 @@ import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import javax.inject.Inject
internal class ReferralRepositoryImpl(
internal class ReferralRepositoryImpl @Inject constructor(
private val referralApi: ReferralApi,
private val referralConverter: ReferralConverter,
private val coroutineDispatcher: CoroutineDispatcherProvider,

View file

@ -26,7 +26,7 @@ internal class ReferralInteractorImpl(
if (tokensForReferral.isNotEmpty()) {
val currency = tokensConverter.convert(tokensForReferral.first())
deriveOrAddTokens(currency)
val publicAddress = userWalletManager.getWalletAddress(currency)
val publicAddress = userWalletManager.getWalletAddress(currency.networkId)
return repository.startReferral(
walletId = userWalletManager.getWalletId(),
networkId = currency.networkId,

View file

@ -31,8 +31,6 @@ 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)

View file

@ -15,7 +15,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import javax.inject.Inject
class SwapRepositoryImpl @Inject constructor(
internal class SwapRepositoryImpl @Inject constructor(
private val tangemTechApi: TangemTechApi,
private val oneInchApi: OneInchApi,
private val tokensConverter: TokensConverter,

View file

@ -1,6 +1,18 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
kotlin("kapt")
}
dependencies {
/** Libs */
implementation(project(":libs:crypto"))
implementation(project(":core:utils"))
/** DI */
implementation(Library.hiltCore)
kapt(Library.hiltKapt)
}
java {

View file

@ -1,14 +1,14 @@
package com.tangem.feature.swap.domain
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.SwapResultModel
interface SwapInteractor {
suspend fun getTokensToSwap(networkId: String): List<Currency>
suspend fun getTokenBalance(): String
suspend fun getTokenBalance(tokenId: String): String
suspend fun findBestQuote(
fromTokenAddress: String,

View file

@ -1,47 +1,62 @@
package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.models.SwapDataHolder
import com.tangem.feature.swap.domain.cache.SwapDataCache
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
import com.tangem.lib.crypto.UserWalletManager
import javax.inject.Inject
//todo add @Inject after merge referral and add HiltCore
class SwapInteractorImpl(
internal class SwapInteractorImpl @Inject constructor(
private val userWalletManager: UserWalletManager,
private val repository: SwapRepository,
private val cache: SwapDataCache,
) : SwapInteractor {
private var lastDataForSwap: SwapDataHolder? = null
override suspend fun getTokensToSwap(networkId: String): List<Currency> {
return repository.getExchangeableTokens(networkId)
val availableTokens = cache.getAvailableTokens(networkId)
return availableTokens.ifEmpty {
val tokens = repository.getExchangeableTokens(networkId)
cache.cacheAvailableToSwapTokens(networkId, tokens)
cache.cacheNetworkId(networkId)
tokens
}
}
override suspend fun getTokenBalance(): String {
override suspend fun getTokenBalance(tokenId: String): 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
val networkId = cache.getNetworkId()
val isAllowedToSpend = if (networkId.isNullOrEmpty()) {
false
} else {
repository.checkTokensSpendAllowance(fromTokenAddress, userWalletManager.getWalletAddress(networkId)) != "0"
}
repository.findBestQuote(fromTokenAddress, toTokenAddress, amount).let { quotes ->
cache.cacheSwapParams(quotes.copy(isAllowedToSpend = isAllowedToSpend), amount)
return quotes
}
}
override suspend fun onSwap(): SwapResultModel {
val dataForSwap = lastDataForSwap
if (dataForSwap != null) {
val quoteModel = cache.getLastQuote()
val amountToSwap = cache.getAmountToSwap()
val networkId = cache.getNetworkId()
if (quoteModel != null && amountToSwap != null && networkId != null) {
val swapData = repository.prepareSwapTransaction(
fromTokenAddress = dataForSwap.quoteModel.fromTokenAmount,
toTokenAddress = dataForSwap.quoteModel.toTokenAmount,
amount = dataForSwap.amount,
fromTokenAddress = quoteModel.fromTokenAmount,
toTokenAddress = quoteModel.toTokenAmount,
amount = amountToSwap,
slippage = DEFAULT_SLIPPAGE,
fromWalletAddress = getWalletAddress(),
fromWalletAddress = getWalletAddress(networkId),
)
signTransactionData(swapData.transaction) //todo implement
return SwapResultModel.SwapSuccess(
dataForSwap.quoteModel.fromTokenAmount,
dataForSwap.quoteModel.toTokenAmount,
quoteModel.fromTokenAmount,
quoteModel.toTokenAmount,
)
}
return SwapResultModel.SwapError(
@ -50,9 +65,8 @@ class SwapInteractorImpl(
)
}
//todo implement after merge referral
private fun getWalletAddress(): String {
return ""
private fun getWalletAddress(networkId: String): String {
return userWalletManager.getWalletAddress(networkId)
}
//todo implement after merge referral
@ -60,6 +74,6 @@ class SwapInteractorImpl(
}
companion object {
private const val DEFAULT_SLIPPAGE = 1
private const val DEFAULT_SLIPPAGE = 2
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.feature.swap.domain.cache
import com.tangem.feature.swap.domain.models.data.Currency
import com.tangem.feature.swap.domain.models.data.QuoteModel
interface SwapDataCache {
fun cacheNetworkId(networkId: String)
fun cacheSwapParams(quoteModel: QuoteModel, amount: String)
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
fun getAvailableTokens(networkId: String): List<Currency>
fun getLastQuote(): QuoteModel?
fun getAmountToSwap(): String?
fun getNetworkId(): String?
}

View file

@ -0,0 +1,39 @@
package com.tangem.feature.swap.domain.cache
import com.tangem.feature.swap.domain.models.SwapDataHolder
import com.tangem.feature.swap.domain.models.data.Currency
import com.tangem.feature.swap.domain.models.data.QuoteModel
class SwapDataCacheImpl : SwapDataCache {
private var lastDataForSwap: SwapDataHolder = SwapDataHolder()
private val availableTokensForNetwork: MutableMap<String, List<Currency>> = mutableMapOf()
override fun cacheSwapParams(quoteModel: QuoteModel, amount: String) {
lastDataForSwap = lastDataForSwap.copy(quoteModel = quoteModel, amountToSwap = amount)
}
override fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>) {
availableTokensForNetwork[networkId] = tokens
}
override fun cacheNetworkId(networkId: String) {
lastDataForSwap = lastDataForSwap.copy(networkId = networkId)
}
override fun getNetworkId(): String? {
return lastDataForSwap.networkId
}
override fun getAvailableTokens(networkId: String): List<Currency> {
return availableTokensForNetwork.getOrElse(networkId) { emptyList() }
}
override fun getLastQuote(): QuoteModel? {
return lastDataForSwap.quoteModel
}
override fun getAmountToSwap(): String? {
return lastDataForSwap.amountToSwap
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.feature.swap.domain.di
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.UserWalletManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class SwapDomainModule {
@Provides
@Singleton
fun provideSwapInteractor(
referralRepository: SwapRepository,
userWalletManager: UserWalletManager,
): SwapInteractor {
return SwapInteractorImpl(
userWalletManager = userWalletManager,
repository = referralRepository,
cache = SwapDataCacheImpl(),
)
}
}

View file

@ -3,6 +3,7 @@ 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,
val quoteModel: QuoteModel? = null,
val amountToSwap: String? = null,
val networkId: String? = null,
)

View file

@ -3,5 +3,6 @@ package com.tangem.feature.swap.domain.models.data
data class QuoteModel(
val fromTokenAmount: String,
val toTokenAmount: String,
val estimatedGas: Int
val estimatedGas: Int,
val isAllowedToSpend: Boolean = false,
)

View file

@ -1,33 +1,29 @@
package com.tangem.feature.swap.presentation
import android.os.Bundle
import android.util.Log
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import com.tangem.feature.swap.domain.SwapInteractorImpl
import com.tangem.feature.swap.domain.SwapRepository
import androidx.fragment.app.viewModels
import com.tangem.feature.swap.viewmodels.SwapViewModel
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
private val viewModel by viewModels<SwapViewModel>()
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())
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
viewModel
return ComposeView(inflater.context).apply {
setContent {
//todo add compose screen here
}
}
return super.onCreateView(inflater, container, savedInstanceState)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.feature.swap.viewmodels
import androidx.lifecycle.ViewModel
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
internal class SwapViewModel @Inject constructor(
private val referralInteractor: SwapInteractor,
private val dispatchers: CoroutineDispatcherProvider,
) : ViewModel() {
}

View file

@ -34,7 +34,7 @@ interface UserWalletManager {
/**
* Returns wallet public address for token
*
* @param currency for which find address
* @param networkId for currency
*/
fun getWalletAddress(currency: Currency): String
fun getWalletAddress(networkId: String): String
}