Updated on 2026-08-14

This commit is contained in:
Tangem 2022-12-09 17:37:56 +03:00
commit 87c43ab20f
83 changed files with 988 additions and 141 deletions

View file

@ -108,10 +108,10 @@ repositories {
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
implementation(project(":domain"))
implementation(project(":network"))
implementation(project(":common"))
implementation(project(":core:res"))
implementation(project(":core:ui"))
implementation(project(":core:datasource"))
implementation(project(":core:utils"))
implementation(project(":libs:crypto"))
implementation(project(":libs:auth"))
@ -120,6 +120,9 @@ dependencies {
implementation(project(":features:referral:presentation"))
implementation(project(":features:referral:domain"))
implementation(project(":features:referral:data"))
implementation(project(":features:swap:presentation"))
implementation(project(":features:swap:domain"))
implementation(project(":features:swap:data"))
/** AndroidX libraries */
implementation(AndroidX.coreKtx)

@ -1 +1 @@
Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70
Subproject commit 897f0f7c402fd53c5ee1fa4ab601ecd4e43ff389

View file

@ -11,9 +11,9 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.Analytics
@ -156,6 +156,7 @@ class TapApplication : Application(), ImageLoaderFactory {
appStateHolder.userTokensRepository = userTokensRepository
}
//todo refactor: move to datasource and provide via DI
private fun initMoshiConverter() {
fun appAdapters(): List<Any> = listOf(
BigDecimalAdapter(),

View file

@ -2,7 +2,7 @@ package com.tangem.tap.domain
import com.tangem.common.services.Result
import com.tangem.domain.common.ThrottlerWithValues
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import java.math.BigDecimal

View file

@ -2,7 +2,7 @@ package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.common.core.TangemError
import com.tangem.network.api.tangemTech.TangemTechError
import com.tangem.datasource.api.tangemTech.TangemTechError
import com.tangem.wallet.R
interface TapErrors

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.tokens
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.CoinsResponse
@JsonClass(generateAdapter = true)
data class CurrencyFromJson(

View file

@ -6,9 +6,9 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.getListOfCoins
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.common.MoshiConverter
import com.tangem.datasource.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.tap.common.AssetReader
class LoadAvailableCoinsService(

View file

@ -7,8 +7,8 @@ import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.getTokens
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.common.MoshiConverter
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.tap.common.FileReader
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.tokens.models.TokenDao

View file

@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.api.tangemTech.UserTokensResponse
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.UserTokensResponse
import com.tangem.tap.domain.NoDataError
class UserTokensNetworkService(private val tangemTechService: TangemTechService) {

View file

@ -3,10 +3,10 @@ package com.tangem.tap.domain.tokens
import android.content.Context
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.services.Result
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.UserTokensResponse
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.util.userWalletId
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.api.tangemTech.UserTokensResponse
import com.tangem.tap.common.AndroidFileReader
import com.tangem.tap.domain.NoDataError
import com.tangem.tap.domain.tokens.models.BlockchainNetwork

View file

@ -2,9 +2,9 @@ package com.tangem.tap.domain.tokens
import com.squareup.moshi.JsonAdapter
import com.tangem.Log
import com.tangem.datasource.api.tangemTech.UserTokensResponse
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.domain.common.CardDTO
import com.tangem.network.api.tangemTech.UserTokensResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.FileReader
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.toCurrencies

View file

@ -10,7 +10,7 @@ import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.tangemSdkManager

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.walletStores.repository.di
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository

View file

@ -14,9 +14,9 @@ import com.tangem.common.flatMap
import com.tangem.common.flatMapOnFailure
import com.tangem.common.map
import com.tangem.common.services.Result
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.util.UserWalletId
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.replaceByOrAdd
import com.tangem.tap.domain.model.UserWallet

View file

@ -7,7 +7,7 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.network.common.MoshiConverter
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
import com.trustwallet.walletconnect.models.WCPeerMeta
@ -18,7 +18,7 @@ import java.nio.charset.Charset
class WalletConnectRepository(val context: Application) {
private val moshi = MoshiConverter.defaultMoshi()
private val walletConnectAdapter: JsonAdapter<List<SessionDao>> = moshi.adapter(
Types.newParameterizedType(List::class.java, SessionDao::class.java)
Types.newParameterizedType(List::class.java, SessionDao::class.java),
)
fun saveSession(session: WalletConnectSession) {
@ -76,7 +76,6 @@ class WalletConnectRepository(val context: Application) {
}
}
@JsonClass(generateAdapter = true)
data class SessionDao(
val peerId: String,
@ -91,7 +90,7 @@ data class SessionDao(
remotePeerId = remotePeerId,
wallet = wallet,
session = session,
peerMeta = peerMeta
peerMeta = peerMeta,
)
}
@ -102,7 +101,7 @@ data class SessionDao(
remotePeerId = session.remotePeerId,
wallet = session.wallet,
session = session.session,
peerMeta = session.peerMeta
peerMeta = session.peerMeta,
)
}
}

View file

@ -12,13 +12,13 @@ import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.successOr
import com.tangem.domain.common.util.UserWalletId
import com.tangem.network.api.paymentology.AttestationResponse
import com.tangem.network.api.paymentology.PaymentologyApiService
import com.tangem.network.api.paymentology.RegisterKYCRequest
import com.tangem.network.api.paymentology.RegisterWalletRequest
import com.tangem.network.api.paymentology.RegisterWalletResponse
import com.tangem.network.api.paymentology.RegistrationResponse
import com.tangem.network.api.paymentology.tryExtractError
import com.tangem.datasource.api.paymentology.AttestationResponse
import com.tangem.datasource.api.paymentology.PaymentologyApiService
import com.tangem.datasource.api.paymentology.RegisterKYCRequest
import com.tangem.datasource.api.paymentology.RegisterWalletRequest
import com.tangem.datasource.api.paymentology.RegisterWalletResponse
import com.tangem.datasource.api.paymentology.RegistrationResponse
import com.tangem.datasource.api.paymentology.tryExtractError
import com.tangem.operations.attestation.AttestWalletKeyResponse
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.getFirstToken
@ -152,12 +152,13 @@ class SaltPayActivationManager(
)
}
private fun makeRegisterKYCRequest(): RegisterKYCRequest = RegisterKYCRequest(
cardId = cardId,
publicKey = cardPublicKey,
kycProvider = "UTORG",
kycRefId = kycUrlProvider.kycRefId,
)
private fun makeRegisterKYCRequest(): RegisterKYCRequest =
RegisterKYCRequest(
cardId = cardId,
publicKey = cardPublicKey,
kycProvider = "UTORG",
kycRefId = kycUrlProvider.kycRefId,
)
companion object {
fun stub(): SaltPayActivationManager = SaltPayActivationManager(

View file

@ -8,8 +8,8 @@ import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.successOr
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.network.api.paymentology.KYCStatus
import com.tangem.network.api.paymentology.RegistrationResponse
import com.tangem.datasource.api.paymentology.KYCStatus
import com.tangem.datasource.api.paymentology.RegistrationResponse
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification

View file

@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.extensions.guard
import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.ScanResponse
import com.tangem.network.api.paymentology.PaymentologyApiService
import com.tangem.datasource.api.paymentology.PaymentologyApiService
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.extensions.makeSaltPayWalletManager
import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator

View file

@ -5,7 +5,7 @@ import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.network.api.tangemTech.TokenResponse
import com.tangem.datasource.api.tangemTech.TokenResponse
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain

View file

@ -1,8 +1,8 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.common.services.Result
import com.tangem.network.api.tangemTech.CurrenciesResponse
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.CurrenciesResponse
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen

View file

@ -6,7 +6,7 @@ import com.tangem.common.extensions.calculateSha512
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager

View file

@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.domain.common.extensions.withIOContext
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.Currency

View file

@ -3,7 +3,7 @@ package com.tangem.tap.network.payid
import com.squareup.moshi.JsonClass
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.createRetrofitInstance
import retrofit2.Retrofit
class PayIdService {

View file

@ -2,7 +2,7 @@ package com.tangem.tap.network.payid
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.createRetrofitInstance
/**
[REDACTED_AUTHOR]

View file

@ -3,7 +3,7 @@ package com.tangem.tap.persistence
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.network.api.tangemTech.CurrenciesResponse
import com.tangem.datasource.api.tangemTech.CurrenciesResponse
import com.tangem.tap.common.entities.FiatCurrency
/**

View file

@ -4,7 +4,7 @@ import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.network.common.MoshiConverter
import com.tangem.datasource.api.common.MoshiConverter
import java.util.*
class PreferencesStorage(applicationContext: Application) {

View file

@ -42,6 +42,9 @@ dependencies {
/** Project */
implementation(project(":libs:auth"))
/** Tangem libraries */
implementation(Tangem.cardCore)
/** DI */
implementation(Library.hilt)
kapt(Library.hiltKapt)

View file

@ -1,4 +1,4 @@
package com.tangem.network.common
package com.tangem.datasource.api.common
import okhttp3.Interceptor
import okhttp3.Response
@ -7,7 +7,7 @@ import okhttp3.Response
[REDACTED_AUTHOR]
*/
open class AddHeaderInterceptor(
private val headers: Map<String, String>
private val headers: Map<String, String>,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
@ -21,10 +21,10 @@ open class AddHeaderInterceptor(
}
}
class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(mapOf(
"Cache-Control" to "max-age=$maxAgeSeconds",
))
class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(
mapOf("Cache-Control" to "max-age=$maxAgeSeconds"),
)
class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf(
"card_public_key" to cardPublicKeyHex,
))
class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(
mapOf("card_public_key" to cardPublicKeyHex),
)

View file

@ -1,4 +1,4 @@
package com.tangem.network.common
package com.tangem.datasource.api.common
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
@ -9,8 +9,10 @@ import retrofit2.converter.moshi.MoshiConverterFactory
/**
[REDACTED_AUTHOR]
*/
//todo needs to be refactored
object MoshiConverter {
//todo refactor: provide via DI
var INSTANCE = MoshiJsonConverter()
private set
@ -23,7 +25,9 @@ object MoshiConverter {
fun createFactory(moshi: Moshi = INSTANCE.moshi): Converter.Factory = MoshiConverterFactory.create(moshi)
//todo provide via DI using quealifiers
fun defaultMoshi(): Moshi = INSTANCE.moshi
//todo provide via DI using quealifiers
fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi
}

View file

@ -1,4 +1,4 @@
package com.tangem.network.common
package com.tangem.datasource.api.common
import okhttp3.Interceptor
import okhttp3.OkHttpClient

View file

@ -0,0 +1,200 @@
package com.tangem.datasource.api.oneinch
import com.tangem.datasource.api.oneinch.models.AllowanceResponse
import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse
import com.tangem.datasource.api.oneinch.models.ApproveSpenderResponse
import com.tangem.datasource.api.oneinch.models.ProtocolsResponse
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.http.GET
import retrofit2.http.Query
interface OneInchApi {
/**
* Healthcheck return 200 if service is available
*
* @return [StatusResponse]
*/
@GET("healthcheck")
suspend fun healthcheck(): StatusResponse
//region Approve
/**
* Address of the 1inch router that must be trusted to spend funds for the exchange
*
* @return [ApproveSpenderResponse]
*/
@GET("approve/spender")
suspend fun approveSpender(): ApproveSpenderResponse
/**
* Generate data for calling the contract in order to allow the 1inch router to spend funds
*
* @param tokenAddress Token address you want to exchange
* @param amount The number of tokens that the 1inch router is allowed to spend.
* If not specified, it will be allowed to spend an infinite amount of tokens.
*
* @return [ApproveCalldataResponse] Transaction body to allow the exchange with the 1inch router
*/
@GET("approve/transaction")
suspend fun approveTransaction(
@Query("tokenAddress") tokenAddress: String,
@Query("amount") amount: String? = null,
): ApproveCalldataResponse
/**
* Get the number of tokens that the 1inch router is allowed to spend
*
* @param tokenAddress Token address you want to exchange
* @param walletAddress Wallet address for which you want to check
*
* @return [AllowanceResponse]
*/
@GET("approve/allowance")
suspend fun approveAllowance(
@Query("tokenAddress") tokenAddress: String,
@Query("walletAddress") walletAddress: String,
): AllowanceResponse
//endregion Approve
//region Info
/**
* List of tokens that are available for swap in the 1inch Aggregation protocol
*
* @return [TokensResponse]
*/
@GET("tokens")
suspend fun tokensAvailable(): TokensResponse
/**
* List of liquidity sources that are available for routing in the 1inch Aggregation protocol
*
* @return
*/
@GET("liquidity-sources")
suspend fun liquiditySources(): ProtocolsResponse
//endregion Info
//region Swap
/**
* Find the best quote to exchange via 1inch router
*
* @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
* @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302
* @param amount amount of a token to sell, set in minimal divisible units e.g.:
* 1.00 DAI set as 1000000000000000000
* 51.03 USDC set as 51030000
*
* @param protocols default: all
* @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress,
* the rest will be used as input for a swap
* Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap!
*
* @param gasLimit maximum amount of gas for a swap;
* @param connectorTokens token-connectors can be specified via this parameter.
* The more is set the longer route estimation will take.
* If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap!
*
* @param complexityLevel maximum number of token-connectors to be used in a transaction.
* The more is used the longer route estimation will take
* min: 0; max: 3; default: 2; !should be the same for quote and swap!
*
* @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap!
* @param parts limit maximum number of parts each main route parts can be split into;
* should be the same for a quote and swap
* default: 20; max: 100
*
* @param gasPrice 1inch takes in account gas expenses to determine exchange route.
* It is important to use the same gas price on the quote and swap methods.
* Gas price set in wei: 12.5 GWEI set as 12500000000
* default: fast from network
*
* @return [QuoteResponse]
*/
suspend fun quote(
@Query("fromTokenAddress") fromTokenAddress: String,
@Query("toTokenAddress") toTokenAddress: String,
@Query("amount") amount: String,
@Query("protocols") protocols: String? = null,
@Query("fee") fee: String? = null,
@Query("gasLimit") gasLimit: String? = null,
@Query("connectorTokens") connectorTokens: String? = null,
@Query("complexityLevel") complexityLevel: String? = null,
@Query("mainRouteParts") mainRouteParts: String? = null,
@Query("parts") parts: String? = null,
@Query("gasPrice") gasPrice: String? = null,
): QuoteResponse
/**
* Generate data for calling the 1inch router for exchange
*
* @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
* @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302
* @param amount amount of a token to sell, set in minimal divisible units e.g.:
* 1.00 DAI set as 1000000000000000000
* 51.03 USDC set as 51030000
*
* @param fromAddress The address that calls the 1inch contract
* @param slippage limit of price slippage you are willing to accept in percentage, may be set with decimals.
* &slippage=0.5 means 0.5% slippage is acceptable. Low values increase chances that transaction will fail,
* high values increase chances of front running. min: 0; max: 50;
*
* @param protocols default: all
* @param destinationAddress Receiver of destination currency. default: fromAddress
* @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress,
* the rest will be used as input for a swap
* Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap!
*
* @param permit https://eips.ethereum.org/EIPS/eip-2612
* @param compatibilityMode Allows to build calldata without optimized routers
* @param burnChi If true, CHI will be burned from fromAddress to compensate gas.
* Check CHI balance and allowance before turning that on. CHI should be approved for the spender address
*
* @param connectorTokens token-connectors can be specified via this parameter.
* The more is set the longer route estimation will take.
* If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap!
*
* @param complexityLevel maximum number of token-connectors to be used in a transaction.
* The more is used the longer route estimation will take
* min: 0; max: 3; default: 2; !should be the same for quote and swap!
*
* @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap!
* @param parts limit maximum number of parts each main route parts can be split into;
* should be the same for a quote and swap
* default: 20; max: 100
*
* @param gasLimit maximum amount of gas for a swap;
* @param gasPrice 1inch takes in account gas expenses to determine exchange route.
* It is important to use the same gas price on the quote and swap methods.
* Gas price set in wei: 12.5 GWEI set as 12500000000
* default: fast from network
*
* @return [SwapResponse]
*/
suspend fun swap(
@Query("fromTokenAddress") fromTokenAddress: String,
@Query("toTokenAddress") toTokenAddress: String,
@Query("amount") amount: String,
@Query("fromAddress") fromAddress: String,
@Query("slippage") slippage: Int,
@Query("protocols") protocols: String? = null,
@Query("destReceiver") destinationAddress: String? = null,
@Query("referrerAddress") referrerAddress: String? = null,
@Query("fee") fee: String? = null,
@Query("disableEstimate") disableEstimate: Boolean? = null,
@Query("permit") permit: String? = null,
@Query("compatibilityMode") compatibilityMode: Boolean? = null,
@Query("burnChi") burnChi: Boolean? = null,
@Query("allowPartialFill") allowPartialFill: Boolean? = null,
@Query("parts") parts: String? = null,
@Query("mainRouteParts") mainRouteParts: String? = null,
@Query("connectorTokens") connectorTokens: String? = null,
@Query("complexityLevel") complexityLevel: String? = null,
@Query("gasLimit") gasLimit: String? = null,
@Query("gasPrice") gasPrice: String? = null,
): SwapResponse
//endregion Swap
}

View file

@ -0,0 +1,7 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
data class AllowanceResponse(
@Json(name = "allowance") val allowance: String,
)

View file

@ -0,0 +1,18 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Approve calldata response
*
* @property data The encoded data to call the approve method on the swapped token contract
* @property gasPrice Gas price for fast transaction processing
* @property toAddress Token address that will be allowed to exchange through 1inch router
* @property value Native token value in WEI (for approve is always 0)
*/
data class ApproveCalldataResponse(
@Json(name = "data") val data: String,
@Json(name = "gasPrice") val gasPrice: String,
@Json(name = "to") val toAddress: String,
@Json(name = "value") val value: String,
)

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Approve spender response
*
* @property address Address of the 1inch router that must be trusted to spend funds for the exchange
*/
data class ApproveSpenderResponse(
@Json(name = "address") val address: String,
)

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Path view dto
*/
data class PathViewDto(
@Json(name = "name") val name: String,
@Json(name = "part") val part: Int,
@Json(name = "fromTokenAddress") val fromTokenAddress: String,
@Json(name = "toTokenAddress") val toTokenAddress: String,
)

View file

@ -0,0 +1,27 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Protocols response
*
* @property protocols List of protocols that are available for routing in the 1inch Aggregation protocol
*/
data class ProtocolsResponse(
@Json(name = "protocols") val protocols: List<ProtocolImageDto>,
)
/**
* Protocol image
*
* @property id Protocol id
* @property title Protocol title
* @property image Protocol logo image
* @property imageColor Protocol logo image in color
*/
data class ProtocolImageDto(
@Json(name = "id") val id: String,
@Json(name = "title") val title: String,
@Json(name = "img") val image: String,
@Json(name = "img_color") val imageColor: String,
)

View file

@ -0,0 +1,22 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Quote response
*
* @property fromToken Source token info
* @property toToken Destination token info
* @property toTokenAmount Expected amount of destination token
* @property fromTokenAmount Amount of source token
* @property protocols Selected protocols in a path
* @property estimatedGas gas fee
*/
data class QuoteResponse(
@Json(name = "fromToken") val fromToken: TokenOneInchDto,
@Json(name = "toToken") val toToken: TokenOneInchDto,
@Json(name = "toTokenAmount") val toTokenAmount: String,
@Json(name = "fromTokenAmount") val fromTokenAmount: String,
@Json(name = "protocols") val protocols: List<PathViewDto>,
@Json(name = "estimatedGas") val estimatedGas: Int,
)

View file

@ -0,0 +1,8 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
data class StatusResponse(
@Json(name = "status") val status: String,
@Json(name = "provider") val provider: String,
)

View file

@ -0,0 +1,42 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Swap error dto
*
* One of the following errors:
*
* -Insufficient liquidity
* -Cannot estimate
* -You may not have enough ETH balance for gas fee
* -FromTokenAddress cannot be equals to toTokenAddress
* -Cannot estimate. Don't forget about miner fee. Try to leave the buffer of ETH for gas
* -Not enough balance
* -Not enough allowance
*
* @property statusCode HTTP code
* @property error Error code description
* @property description Error description (one of the following)
* @property requestId Request id
* @property meta Meta information
* @constructor Create empty Swap error dto
*/
data class SwapErrorDto(
@Json(name = "statusCode") val statusCode: Int,
@Json(name = "error") val error: String,
@Json(name = "description") val description: String,
@Json(name = "requestId") val requestId: String,
@Json(name = "meta") val meta: List<NestErrorMeta>,
)
/**
* Nest error meta
*
* @property type Type of field
* @property value Value of field
*/
data class NestErrorMeta(
@Json(name = "type") val type: String,
@Json(name = "value") val value: String,
)

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
data class SwapResponse(
@Json(name = "fromToken") val fromToken: TokenOneInchDto,
@Json(name = "toToken") val toToken: TokenOneInchDto,
@Json(name = "toTokenAmount") val toTokenAmount: String,
@Json(name = "fromTokenAmount") val fromTokenAmount: String,
@Json(name = "protocols") val protocols: List<PathViewDto>,
@Json(name = "transaction") val transaction: TransactionDto,
)

View file

@ -0,0 +1,20 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Token one inch
*
* @property symbol token symbol
* @property name token name
* @property address token address
* @property decimals token decimals
* @property logoURI token logo image url
*/
data class TokenOneInchDto(
@Json(name = "symbol") val symbol: String,
@Json(name = "name") val name: String,
@Json(name = "address") val address: String,
@Json(name = "decimals") val decimals: Int,
@Json(name = "logoURI") val logoURI: String,
)

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Tokens response
*
* @property tokens List of supported tokens
*/
data class TokensResponse(
@Json(name = "tokens") val tokens: Map<String, TokenOneInchDto>,
)

View file

@ -0,0 +1,23 @@
package com.tangem.datasource.api.oneinch.models
import com.squareup.moshi.Json
/**
* Transaction dto
*
* @property fromAddress transactions will be sent from this address
* @property toAddress 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 Transaction dto
*/
data class TransactionDto(
@Json(name = "from") val fromAddress: String,
@Json(name = "to") val toAddress: String,
@Json(name = "data") val data: String,
@Json(name = "value") val value: String,
@Json(name = "gasPrice") val gasPrice: String,
@Json(name = "gas") val gas: String,
)

View file

@ -1,4 +1,4 @@
package com.tangem.network.api.paymentology
package com.tangem.datasource.api.paymentology
import retrofit2.http.Body
import retrofit2.http.Headers

View file

@ -1,10 +1,10 @@
package com.tangem.network.api.paymentology
package com.tangem.datasource.api.paymentology
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.MoshiConverter
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.createRetrofitInstance
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View file

@ -1,4 +1,4 @@
package com.tangem.network.api.paymentology
package com.tangem.datasource.api.paymentology
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.network.api.paymentology
package com.tangem.datasource.api.paymentology
import com.squareup.moshi.Json
import com.tangem.common.extensions.calculateHashCode

View file

@ -1,4 +1,4 @@
package com.tangem.network.api.tangemTech
package com.tangem.datasource.api.tangemTech
import java.math.BigDecimal
@ -11,7 +11,7 @@ sealed interface TangemTechResponse : HttpResponse
data class CoinsResponse(
val imageHost: String?,
val coins: List<Coin>,
val total: Int
val total: Int,
) : TangemTechResponse {
data class Coin(
@ -19,13 +19,14 @@ data class CoinsResponse(
val name: String,
val symbol: String,
val active: Boolean,
val networks: List<Network> = listOf()
val networks: List<Network> = listOf(),
) : TangemTechResponse {
data class Network(
val networkId: String,
val contractAddress: String? = null,
val decimalCount: BigDecimal? = null,
val exchangeable: Boolean? = false,
) : TangemTechResponse
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.network.api.tangemTech
package com.tangem.datasource.api.tangemTech
import retrofit2.http.Body
import retrofit2.http.GET
@ -14,11 +14,12 @@ interface TangemTechApi {
@GET("coins")
suspend fun coins(
@Query("contractAddress") contractAddress: String? = null,
@Query("exchangeable") exchangeable: Boolean? = false,
@Query("networkIds") networkIds: String? = null,
@Query("active") active: Boolean? = null,
@Query("searchText") searchText: String? = null,
@Query("offset") offset: Int? = null,
@Query("limit") limit: Int? = null
@Query("limit") limit: Int? = null,
): CoinsResponse
@GET("rates")

View file

@ -1,10 +1,10 @@
package com.tangem.network.api.tangemTech
package com.tangem.datasource.api.tangemTech
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.AddHeaderInterceptor
import com.tangem.network.common.CacheControlHttpInterceptor
import com.tangem.network.common.createRetrofitInstance
import com.tangem.datasource.api.common.AddHeaderInterceptor
import com.tangem.datasource.api.common.CacheControlHttpInterceptor
import com.tangem.datasource.api.common.createRetrofitInstance
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View file

@ -3,7 +3,10 @@ package com.tangem.datasource.di
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.datasource.api.AuthHeaderInterceptor
import com.tangem.datasource.api.oneinch.OneInchApi
import com.tangem.datasource.api.referral.ReferralApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.qualifiers.OneInchEthereum
import com.tangem.lib.auth.AuthProvider
import dagger.Module
import dagger.Provides
@ -19,6 +22,33 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
class NetworkModule {
@Provides
@Singleton
fun provideTangemTechApi(okHttpClient: OkHttpClient): TangemTechApi {
return Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(),
)
.baseUrl(DEV_TANGEM_TECH_BASE_URL)
.client(okHttpClient)
.build()
.create(TangemTechApi::class.java)
}
@Provides
@Singleton
@OneInchEthereum
fun provideOneInchEthereumApi(): OneInchApi {
return Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(),
)
.baseUrl(ONE_INCH_ETHER_BASE_URL)
.client(OkHttpClient())
.build()
.create(OneInchApi::class.java)
}
@Provides
@Singleton
fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi {
@ -30,7 +60,7 @@ class NetworkModule {
.build(),
),
)
.baseUrl(PROD_REFERRAL_BASE_URL)
.baseUrl(DEV_TANGEM_TECH_BASE_URL)
.client(okHttpClient)
.build()
.create(ReferralApi::class.java)
@ -48,6 +78,8 @@ class NetworkModule {
}
private companion object {
const val PROD_REFERRAL_BASE_URL = "https://devapi.tangem-tech.com/v1/"//"https://api.tangem-tech.com/v1/"
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 = ""
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.datasource.di.qualifiers
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OneInchEthereum
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OneInchBinance
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OneInchOptimism

View file

@ -71,7 +71,7 @@ android {
}
dependencies {
implementation(project(":network"))
implementation(project(":core:datasource"))
implementation(project(":common"))
/** Tangem libraries */

View file

@ -1,7 +1,7 @@
package com.tangem.domain
import com.tangem.common.extensions.VoidCallback
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.CoinsResponse
/**
[REDACTED_AUTHOR]

View file

@ -1,8 +1,8 @@
package com.tangem.domain.common.extensions
import com.tangem.common.services.Result
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.TangemTechService
suspend fun TangemTechService.getTokens(
contractAddress: String,

View file

@ -2,8 +2,8 @@ package com.tangem.domain.features.addCustomToken
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.getTokens
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.TangemTechService
/**
[REDACTED_AUTHOR]

View file

@ -8,7 +8,7 @@ import com.tangem.domain.common.form.Field
import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.CoinsResponse
import org.rekotlin.Action
/**

View file

@ -56,7 +56,7 @@ import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.extensions.dispatchOnMain
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.domain.redux.global.DomainGlobalState
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.CoinsResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View file

@ -37,7 +37,7 @@ import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
import com.tangem.domain.features.addCustomToken.TokenField
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.state.StringActionStateConverter
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.datasource.api.tangemTech.CoinsResponse
import org.rekotlin.Action
import org.rekotlin.StateType

View file

@ -5,7 +5,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.domain.redux.BaseStoreHub
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.ReStoreReducer
import com.tangem.network.common.CardPublicKeyHttpInterceptor
import com.tangem.datasource.api.common.CardPublicKeyHttpInterceptor
import org.rekotlin.Action
/**

View file

@ -3,8 +3,8 @@ package com.tangem.domain.redux.global
import com.tangem.domain.DomainDialog
import com.tangem.domain.common.LogConfig
import com.tangem.domain.common.ScanResponse
import com.tangem.network.api.paymentology.PaymentologyApiService
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.datasource.api.paymentology.PaymentologyApiService
import com.tangem.datasource.api.tangemTech.TangemTechService
/**
[REDACTED_AUTHOR]
@ -19,6 +19,8 @@ data class DomainGlobalState(
)
data class NetworkServices(
val tangemTechService: TangemTechService = TangemTechService(LogConfig.network.tangemTechService),
val paymentologyService: PaymentologyApiService = PaymentologyApiService(LogConfig.network.paymentologyApiService),
val tangemTechService: TangemTechService = TangemTechService(
LogConfig.network.tangemTechService),
val paymentologyService: PaymentologyApiService = PaymentologyApiService(
LogConfig.network.paymentologyApiService),
)

View file

@ -0,0 +1,37 @@
plugins {
id("com.android.library")
kotlin("android")
kotlin("kapt")
id("com.google.dagger.hilt.android")
}
android {
defaultConfig {
compileSdk = AppConfig.compileSdkVersion
minSdk = AppConfig.minSdkVersion
targetSdk = AppConfig.targetSdkVersion
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = false
}
}
dependencies {
/** Project*/
implementation(project(":core:datasource"))
implementation(project(":core:utils"))
implementation(project(":features:swap:domain"))
/** DI */
implementation(Library.hilt)
kapt(Library.hiltKapt)
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tangem.feature.swap.data" />

View file

@ -0,0 +1,82 @@
package com.tangem.feature.swap
import com.tangem.datasource.api.oneinch.OneInchApi
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.ApproveModel
import com.tangem.feature.swap.domain.models.Currency
import com.tangem.feature.swap.domain.models.QuoteModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import javax.inject.Inject
class SwapRepositoryImpl @Inject constructor(
private val tangemTechApi: TangemTechApi,
private val oneInchApi: OneInchApi,
private val tokensConverter: TokensConverter,
private val quotesConverter: QuotesConverter,
private val swapConverter: SwapConverter,
private val approveConverter: ApproveConverter,
private val coroutineDispatcher: CoroutineDispatcherProvider,
) : SwapRepository {
override suspend fun getExchangeableTokens(networkId: String): List<Currency> {
return withContext(coroutineDispatcher.io) {
tokensConverter.convertList(tangemTechApi.coins(exchangeable = true, networkIds = networkId).coins)
}
}
override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel {
return withContext(coroutineDispatcher.io) {
quotesConverter.convert(
oneInchApi.quote(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
),
)
}
}
override suspend fun addressForTrust(): String {
return withContext(coroutineDispatcher.io) {
oneInchApi.approveSpender().address
}
}
override suspend fun dataToApprove(tokenAddress: String, amount: String): ApproveModel {
return withContext(coroutineDispatcher.io) {
approveConverter.convert(oneInchApi.approveTransaction(tokenAddress, amount))
}
}
override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String {
return withContext(coroutineDispatcher.io) {
oneInchApi.approveAllowance(tokenAddress, walletAddress).allowance
}
}
override suspend fun prepareSwapTransaction(
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromAddress: String,
slippage: Int,
) {
return withContext(coroutineDispatcher.io) {
swapConverter.convert(
oneInchApi.swap(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
fromAddress = fromAddress,
slippage = slippage,
),
)
}
}
}

View file

@ -0,0 +1,17 @@
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.utils.converter.Converter
import javax.inject.Inject
class ApproveConverter @Inject constructor() : Converter<ApproveCalldataResponse, ApproveModel> {
override fun convert(value: ApproveCalldataResponse): ApproveModel {
return ApproveModel(
data = value.data,
gasPrice = value.gasPrice,
toAddress = value.toAddress,
)
}
}

View file

@ -0,0 +1,16 @@
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.utils.converter.Converter
class QuotesConverter : Converter<QuoteResponse, QuoteModel> {
override fun convert(value: QuoteResponse): QuoteModel {
return QuoteModel(
fromTokenAmount = value.fromTokenAmount,
toTokenAmount = value.toTokenAmount,
estimatedGas = value.estimatedGas,
)
}
}

View file

@ -0,0 +1,24 @@
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.utils.converter.Converter
import javax.inject.Inject
class SwapConverter @Inject constructor() : Converter<SwapResponse, SwapTransactionModel> {
override fun convert(value: SwapResponse): SwapTransactionModel {
return SwapTransactionModel(
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,
)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.feature.swap.converters
import com.tangem.datasource.api.tangemTech.CoinsResponse
import com.tangem.feature.swap.domain.models.Currency
import com.tangem.utils.converter.Converter
import javax.inject.Inject
class TokensConverter @Inject constructor() : Converter<CoinsResponse.Coin, Currency> {
override fun convert(value: CoinsResponse.Coin): Currency {
val network = value.networks.first()
return if (network.contractAddress != null && network.decimalCount != null) {
Currency.NonNativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = network.networkId,
contractAddress = network.contractAddress!!,
decimalCount = network.decimalCount!!.intValueExact(),
logoUrl = "",//todo add logo
)
} else {
Currency.NativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = network.networkId,
logoUrl = "", //todo add logo
)
}
}
}

1
features/swap/domain/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,9 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View file

@ -0,0 +1,4 @@
package com.tangem.feature.swap.domain
interface SwapInteractor {
}

View file

@ -0,0 +1,52 @@
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
interface SwapRepository {
suspend fun getExchangeableTokens(networkId: String): List<Currency>
suspend fun findBestQuote(
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
): QuoteModel
/**
* Returns address of 1inch router that must be trusted
*
* @return address
*/
suspend fun addressForTrust(): String
/**
* Generate "data" for calling contract in order to allow 1inch spend funds
*
* @param tokenAddress token you want to exchange
* @param amount number of tokens is allowed. By default infinite
*/
suspend fun dataToApprove(
tokenAddress: String,
amount: String,
): ApproveModel
/**
* Get the number of tokens that the 1inch router is allowed to spend
*
* @param tokenAddress Token address you want to exchange
* @param walletAddress address for which you want to check
*
* @return amount of tokens allowed to spend
*/
suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String
suspend fun prepareSwapTransaction(
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromAddress: String,
slippage: Int,
)
}

View file

@ -0,0 +1,14 @@
package com.tangem.feature.swap.domain.models
/**
* Approve model
*
* @property data The encoded data to call the approve method on the swapped token contract
* @property gasPrice Gas price for fast transaction processing
* @property toAddress Token address that will be allowed to exchange through 1inch router
*/
data class ApproveModel(
val data: String,
val gasPrice: String,
val toAddress: String,
)

View file

@ -0,0 +1,28 @@
package com.tangem.feature.swap.domain.models
sealed class Currency(
open val id: String,
open val name: String,
open val symbol: String,
open val networkId: String,
open val logoUrl: String,
) {
data class NativeToken(
override val id: String,
override val name: String,
override val symbol: String,
override val networkId: String,
override val logoUrl: String,
) : Currency(id, name, symbol, networkId, logoUrl)
data class NonNativeToken(
override val id: String,
override val name: String,
override val symbol: String,
override val networkId: String,
override val logoUrl: String,
val contractAddress: String,
val decimalCount: Int,
) : Currency(id, name, symbol, networkId, logoUrl)
}

View file

@ -0,0 +1,7 @@
package com.tangem.feature.swap.domain.models
data class QuoteModel(
val fromTokenAmount: String,
val toTokenAmount: String,
val estimatedGas: Int
)

View file

@ -0,0 +1,29 @@
package com.tangem.feature.swap.domain.models
/**
* 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
* @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,
val fromWalletAddress: String,
val toWalletAddress: String,
val data: String,
val value: String,
val gasPrice: String,
val gas: String,
)

1
features/swap/presentation/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,59 @@
plugins {
id("com.android.library")
kotlin("android")
kotlin("kapt")
id("com.google.dagger.hilt.android")
}
android {
defaultConfig {
compileSdk = AppConfig.compileSdkVersion
minSdk = AppConfig.minSdkVersion
targetSdk = AppConfig.targetSdkVersion
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = false
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.compose
}
}
dependencies {
/** Core modules */
implementation(project(":core:utils"))
implementation(project(":core:ui"))
/** AndroidX */
implementation(AndroidX.appCompat)
implementation(AndroidX.fragmentKtx)
implementation(AndroidX.lifecycleViewModelKtx)
/** Compose */
implementation(Compose.foundation)
implementation(Compose.material)
implementation(Compose.uiTooling)
/** Domain */
implementation(project(":features:swap:domain"))
/** Other libraries */
implementation(Library.composeShimmer)
/** DI */
implementation(Library.hilt)
kapt(Library.hiltKapt)
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tangem.feature.swap.presentation" />

View file

@ -1,26 +0,0 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation(project(":common"))
/** Tangem libraries */
implementation(Tangem.cardCore)
/** Other libraries */
implementation(Library.retrofit)
implementation(Library.retrofitMoshiConverter)
implementation(Library.moshi)
implementation(Library.moshiKotlin)
implementation(Library.okHttp)
implementation(Library.okHttpLogging)
implementation(Library.timber)
implementation(Library.coroutine)
}

View file

@ -1,23 +0,0 @@
package com.tangem.network.common
import com.tangem.common.module.ModuleError
import com.tangem.common.module.ModuleErrorCode
import com.tangem.common.module.ModuleMessage
/**
[REDACTED_AUTHOR]
*/
sealed interface NetworkModuleMessage : ModuleMessage
sealed class NetworkError(
subCode: Int,
override val message: String,
override val data: Any?,
) : NetworkModuleMessage, ModuleError() {
override val code: Int = ModuleErrorCode.NETWORK + subCode
companion object {
// base code used for all error in the module
// const val CODE_ANY_OTHER = 100..199, 200..299
}
}

View file

@ -1,6 +1,5 @@
include(":app")
include(":domain")
include(":network")
include(":common")
// region Core modules
@ -19,4 +18,7 @@ include(":libs:auth")
include(":features:referral:data")
include(":features:referral:domain")
include(":features:referral:presentation")
include(":features:swap:data")
include(":features:swap:domain")
include(":features:swap:presentation")
// endregion Feature modules