Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-12 16:12:00 +03:00
parent c779af8173
commit ee8c426ca9
9 changed files with 161 additions and 137 deletions

View file

@ -1,7 +1,7 @@
package com.tangem.tap.network.coinmarketcap
import com.tangem.network.common.AddHeaderInterceptor
import com.tangem.network.common.createRetrofitInstance
import okhttp3.Interceptor
import retrofit2.http.GET
import retrofit2.http.Query
@ -9,9 +9,9 @@ interface CoinMarketCapApi {
@GET("v1/tools/price-conversion")
suspend fun getRateInfo(
@Query("amount") amount: Int,
@Query("symbol") cryptoCurrencyName: String,
@Query("convert") fiatCurrencyName: String? = null
@Query("amount") amount: Int,
@Query("symbol") cryptoCurrencyName: String,
@Query("convert") fiatCurrencyName: String? = null
): RateInfoResponse
@GET("v1/fiat/map")
@ -23,15 +23,11 @@ interface CoinMarketCapApi {
fun create(apiKey: String): CoinMarketCapApi {
return createRetrofitInstance(
baseUrl,
interceptors = listOf(createCoinMarketRequestInterceptor(apiKey)),
baseUrl = baseUrl,
interceptors = listOf(
AddHeaderInterceptor(mapOf("X-CMC_PRO_API_KEY" to apiKey)),
),
).create(CoinMarketCapApi::class.java)
}
}
}
private fun createCoinMarketRequestInterceptor(apiKey: String): Interceptor = Interceptor { chain ->
val requestBuilder = chain.request().newBuilder()
requestBuilder.addHeader("X-CMC_PRO_API_KEY", apiKey)
chain.proceed(requestBuilder.build())
}

View file

@ -1,8 +1,6 @@
package com.tangem.tap.network.exchangeServices.onramper
import com.squareup.moshi.JsonClass
import okhttp3.Interceptor
import okhttp3.Response
import retrofit2.http.GET
import retrofit2.http.Path
@ -23,15 +21,6 @@ interface OnramperApi {
}
}
class AddKeyToHeaderInterceptor(
private val key: String
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder().addHeader("Authorization", "Basic $key").build()
return chain.proceed(request)
}
}
@JsonClass(generateAdapter = true)
data class GatewaysResponse(
val gateways: List<OnramperGateway>

View file

@ -4,6 +4,7 @@ import android.net.Uri
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.AddHeaderInterceptor
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
@ -25,7 +26,9 @@ class OnramperService(
private val api: OnramperApi by lazy {
createRetrofitInstance(
baseUrl = OnramperApi.BASE_URL,
interceptors = listOf(AddKeyToHeaderInterceptor(apiKey))
interceptors = listOf(
AddHeaderInterceptor(mapOf("Authorization" to "Basic $apiKey")),
)
).create(OnramperApi::class.java)
}

View file

@ -1,9 +1,9 @@
package com.tangem.domain.features.addCustomToken
import com.tangem.common.services.Result
import com.tangem.network.api.tangemTech.CoinsCheckAddressResponse
import com.tangem.network.api.tangemTech.TangemAuthInterceptor
import com.tangem.network.api.tangemTech.Coins
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.common.AddHeaderInterceptor
/**
[REDACTED_AUTHOR]
@ -12,20 +12,52 @@ class AddCustomTokenManager(
private val tangemTechService: TangemTechService
) {
suspend fun findContractAddress(
suspend fun checkAddress(
contractAddress: String,
networkId: String? = null
): List<CoinsCheckAddressResponse.Token> {
val result = tangemTechService.coinsCheckAddress(contractAddress, networkId)
): List<Coins.CheckAddressResponse.Token> {
val result = tangemTechService.coins.checkAddress(contractAddress, networkId)
return when (result) {
is Result.Success -> {
result.data.tokens
val resultTokens = result.data.tokens
val newTokensList = mutableListOf<Coins.CheckAddressResponse.Token>()
resultTokens.forEach {
val contractsWithTheSameAddress = it.contracts.filter { it.address == contractAddress }
if (contractsWithTheSameAddress.isNotEmpty()) {
val newToken = it.copy(contracts = contractsWithTheSameAddress)
newTokensList.add(newToken)
}
}
when {
// https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679
newTokensList.size > 1 -> listOf(newTokensList[0])
else -> newTokensList
}
}
is Result.Failure -> emptyList()
}
}
suspend fun tokens(): List<Coins.TokensResponse.Token> {
val result = tangemTechService.coins.tokens()
return when (result) {
is Result.Success -> {
val currencies = result.data.tokens
currencies.filter {
it.contracts.isNullOrEmpty()
}
}
is Result.Failure -> emptyList()
}
}
fun attachAuthKey(authKey: String) {
tangemTechService.addHeaderInterceptors(listOf(TangemAuthInterceptor(authKey)))
tangemTechService.addHeaderInterceptors(listOf(
CardPublicKeyHttpInterceptor(authKey),
))
}
}
}
private class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf(
"card_public_key" to cardPublicKeyHex,
))

View file

@ -5,46 +5,57 @@ import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
data class CoinsPricesResponse(
val prices: List<CoinPrice>
)
interface HttpResponse
interface TangemTechResponse : HttpResponse
data class CoinPrice(
val name: String,
val price: BigDecimal,
)
data class CoinsCheckAddressResponse(
val imageHost: String,
val tokens: List<Token>,
val total: Int,
) {
data class Token(
val id: String,
val name: String,
val symbol: String,
val active: Boolean,
val contracts: List<Contract>
) {
data class Contract(
val networkId: String,
val address: String,
val decimalCount: BigDecimal?,
val active: Boolean
sealed class Coins : TangemTechResponse {
data class PricesResponse(val prices: List<Price>) : Coins() {
data class Price(
val name: String,
val price: BigDecimal,
)
}
}
data class CoinsCurrenciesResponse(
val currencies: List<Currency>,
) {
data class Currency(
val id: String,
val code: String,
val name: String,
val rateBTC: String,
val unit: String,
val type: String,
)
}
data class CheckAddressResponse(val imageHost: String, val tokens: List<Token>, val total: Int) : Coins() {
data class Token(
val id: String,
val name: String,
val symbol: String,
val active: Boolean,
val contracts: List<Contract>
) {
data class Contract(
val networkId: String,
val address: String,
val decimalCount: BigDecimal?,
val active: Boolean
)
}
}
data class TokensResponse(val imageHost: String, val tokens: List<Token>, val total: Int) : Coins() {
data class Token(
val id: String,
val name: String,
val symbol: String,
val contracts: List<Contract>?
) {
data class Contract(
val networkId: String,
val address: String,
val decimalCount: BigDecimal?,
)
}
}
data class CurrenciesResponse(val currencies: List<Currency>) {
data class Currency(
val id: String,
val code: String,
val name: String,
val rateBTC: String,
val unit: String,
val type: String,
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.network.api.tangemTech
import com.tangem.common.services.Result
import retrofit2.http.GET
import retrofit2.http.Query
@ -13,18 +12,18 @@ interface TangemTechApi {
suspend fun coinsPrices(
@Query("currency") currency: String,
@Query("ids") ids: List<String>,
): Result<CoinsPricesResponse>
): Coins.PricesResponse
@GET("coins/check-address")
suspend fun coinsCheckAddress(
@Query("contractAddress") contractAddress: String,
@Query("networkId") networkId: String? = null,
): Result<CoinsCheckAddressResponse>
): Coins.CheckAddressResponse
@GET("coins/currencies")
suspend fun coinsCurrencies(): Result<CoinsCurrenciesResponse>
suspend fun coinsCurrencies(): Coins.CurrenciesResponse
@GET("coins/tokens")
suspend fun coinsTokens(): Result<CoinsCurrenciesResponse>
suspend fun coinsTokens(): Coins.TokensResponse
}

View file

@ -1,8 +1,9 @@
package com.tangem.network.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.CacheHttpInterceptor
import com.tangem.network.common.CacheControlHttpInterceptor
import com.tangem.network.common.createRetrofitInstance
/**
@ -10,50 +11,18 @@ import com.tangem.network.common.createRetrofitInstance
*/
class TangemTechService {
val coins: CoinsRoute = CoinsRoute()
private val techRoutes: List<TangemTechRoute> = listOf(
coins
)
private val headerInterceptors = mutableListOf<AddHeaderInterceptor>(
CacheHttpInterceptor(cacheMaxAge)
CacheControlHttpInterceptor(cacheMaxAge)
)
private var api: TangemTechApi = createApi()
suspend fun coinsPrices(
currency: String,
ids: List<String>
): Result<CoinsPricesResponse> {
return try {
api.coinsPrices(currency, ids)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
suspend fun coinsCheckAddress(
contractAddress: String,
networkId: String? = null
): Result<CoinsCheckAddressResponse> {
return try {
api.coinsCheckAddress(contractAddress, networkId)
} catch (ex: Exception) {
Result.Failure(ex)
}
}
suspend fun coinsCurrencies(): Result<CoinsCurrenciesResponse> {
return try {
api.coinsCurrencies()
} catch (ex: Exception) {
Result.Failure(ex)
}
}
suspend fun coinsTokens(): Result<CoinsCurrenciesResponse> {
return try {
api.coinsTokens()
} catch (ex: Exception) {
Result.Failure(ex)
}
}
fun addHeaderInterceptors(interceptors: List<AddHeaderInterceptor>) {
headerInterceptors.removeAll(interceptors)
headerInterceptors.addAll(interceptors)
@ -65,8 +34,9 @@ class TangemTechService {
baseUrl = baseUrl,
interceptors = headerInterceptors.toList()
)
return retrofit.create(TangemTechApi::class.java)
return retrofit.create(TangemTechApi::class.java).apply {
techRoutes.forEach { it.setApi(this) }
}
}
companion object {
@ -75,8 +45,36 @@ class TangemTechService {
}
}
class TangemAuthInterceptor(
private val cardPublicKeyHex: String
) : AddHeaderInterceptor(
mapOf("card_public_key" to cardPublicKeyHex)
)
private interface TangemTechRoute {
fun setApi(api: TangemTechApi)
}
class CoinsRoute : TangemTechRoute {
private lateinit var api: TangemTechApi
override fun setApi(api: TangemTechApi) {
this.api = api
}
suspend fun prices(
currency: String,
ids: List<String>
): Result<Coins.PricesResponse> {
return performRequest { api.coinsPrices(currency, ids) }
}
suspend fun checkAddress(
contractAddress: String,
networkId: String? = null
): Result<Coins.CheckAddressResponse> {
return performRequest { api.coinsCheckAddress(contractAddress, networkId) }
}
suspend fun currencies(): Result<Coins.CurrenciesResponse> {
return performRequest { api.coinsCurrencies() }
}
suspend fun tokens(): Result<Coins.TokensResponse> {
return performRequest { api.coinsTokens() }
}
}

View file

@ -21,6 +21,6 @@ open class AddHeaderInterceptor(
}
}
class CacheHttpInterceptor(
maxAgeSeconds: Int
) : AddHeaderInterceptor(mapOf("Cache-Control" to "max-age=$maxAgeSeconds"))
class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(mapOf(
"Cache-Control" to "max-age=$maxAgeSeconds",
))

View file

@ -15,8 +15,13 @@ fun createRetrofitInstance(
converterFactory: Converter.Factory = MoshiConverter.createFactory(),
logEnabled: Boolean = false
): Retrofit {
okHttpBuilder.apply {
callTimeout(10, TimeUnit.SECONDS)
connectTimeout(20, TimeUnit.SECONDS)
readTimeout(20, TimeUnit.SECONDS)
writeTimeout(20, TimeUnit.SECONDS)
}
interceptors.forEach { okHttpBuilder.addInterceptor(it) }
addTimeOuts(okHttpBuilder)
if (logEnabled) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor())
@ -27,15 +32,6 @@ fun createRetrofitInstance(
.build()
}
private fun addTimeOuts(okHttpBuilder: OkHttpClient.Builder) {
okHttpBuilder.callTimeout(1, TimeUnit.SECONDS)
okHttpBuilder.connectTimeout(20, TimeUnit.SECONDS)
okHttpBuilder.readTimeout(20, TimeUnit.SECONDS)
okHttpBuilder.writeTimeout(20, TimeUnit.SECONDS)
}
private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}