diff --git a/network/.gitignore b/network/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/network/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/network/build.gradle b/network/build.gradle new file mode 100644 index 0000000000..df2d688c42 --- /dev/null +++ b/network/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java-library' + id 'org.jetbrains.kotlin.jvm' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + // Tangem sdk's + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-140' + + // Network + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:logging-interceptor") + implementation 'com.squareup.retrofit2:retrofit:2.8.1' + implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' + implementation 'com.squareup.moshi:moshi:1.13.0' + implementation "com.squareup.moshi:moshi-kotlin:1.13.0" +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt new file mode 100644 index 0000000000..6396f105be --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -0,0 +1,50 @@ +package com.tangem.network.api.tangemTech + +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +data class CoinsPricesResponse( + val prices: List +) + +data class CoinPrice( + val name: String, + val price: BigDecimal, +) + +data class CoinsCheckAddressResponse( + val imageHost: String, + val tokens: List, + val total: Int, +) { + data class Token( + val id: String, + val name: String, + val symbol: String, + val active: Boolean, + val contracts: List + ) { + data class Contract( + val networkId: String, + val address: String, + val decimalCount: BigDecimal, + val active: Boolean + ) + } +} + +data class CoinsCurrenciesResponse( + val currencies: List, +) { + data class Currency( + val id: String, + val code: String, + val name: String, + val rateBTC: String, + val unit: String, + val type: String, + ) +} + diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt new file mode 100644 index 0000000000..fbb92c73f1 --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt @@ -0,0 +1,30 @@ +package com.tangem.network.api.tangemTech + +import com.tangem.common.services.Result +import retrofit2.http.GET +import retrofit2.http.Query + +/** +[REDACTED_AUTHOR] + */ +interface TangemTechApi { + + @GET("coins/prices") + suspend fun coinsPrices( + @Query("currency") currency: String, + @Query("ids") ids: List, + ): Result + + @GET("coins/check-address") + suspend fun coinsCheckAddress( + @Query("contractAddress") contractAddress: String, + @Query("networkId") networkId: String? = null, + ): Result + + @GET("coins/currencies") + suspend fun coinsCurrencies(): Result + + @GET("coins/tokens") + suspend fun coinsTokens(): Result + +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt new file mode 100644 index 0000000000..b8e341d428 --- /dev/null +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt @@ -0,0 +1,66 @@ +package com.tangem.network.api.tangemTech + +import com.tangem.common.services.Result +import com.tangem.network.common.AddHeaderInterceptor +import com.tangem.network.common.CacheHttpInterceptor +import com.tangem.network.common.createRetrofitInstance + +/** +[REDACTED_AUTHOR] + */ +class TangemTechService { + + private val headerInterceptors = mutableListOf( + CacheHttpInterceptor(cacheMaxAge) + ) + + private var api: TangemTechApi = createApi() + + suspend fun coinsPrices( + currency: String, + ids: List + ): Result { + return api.coinsPrices(currency, ids) + } + + suspend fun coinsCheckAddress( + contractAddress: String, + networkId: String? = null + ): Result { + return api.coinsCheckAddress(contractAddress, networkId) + } + + suspend fun coinsCurrencies(): Result { + return api.coinsCurrencies() + } + + suspend fun coinsTokens(): Result { + return api.coinsTokens() + } + + fun addHeaderInterceptors(interceptors: List) { + headerInterceptors.removeAll(interceptors) + headerInterceptors.addAll(interceptors) + api = createApi() + } + + private fun createApi(): TangemTechApi { + val retrofit = createRetrofitInstance( + baseUrl = baseUrl, + interceptors = headerInterceptors.toList() + ) + + return retrofit.create(TangemTechApi::class.java) + } + + companion object { + const val baseUrl = "https://api.tangem-tech.com/" + const val cacheMaxAge = 600 + } +} + +class TangemAuthInterceptor( + private val cardPublicKeyHex: String +) : AddHeaderInterceptor( + mapOf("card_public_key" to cardPublicKeyHex) +) \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/Interceptors.kt b/network/src/main/java/com/tangem/network/common/Interceptors.kt new file mode 100644 index 0000000000..dab4451281 --- /dev/null +++ b/network/src/main/java/com/tangem/network/common/Interceptors.kt @@ -0,0 +1,26 @@ +package com.tangem.network.common + +import okhttp3.Interceptor +import okhttp3.Response + +/** +[REDACTED_AUTHOR] + */ +open class AddHeaderInterceptor( + private val headers: Map +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request().newBuilder().apply { + headers.forEach { + addHeader(it.key, it.value) + } + }.build() + + return chain.proceed(request) + } +} + +class CacheHttpInterceptor( + maxAgeSeconds: Int +) : AddHeaderInterceptor(mapOf("Cache-Control" to "max-age=$maxAgeSeconds")) \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt b/network/src/main/java/com/tangem/network/common/MoshiConverter.kt new file mode 100644 index 0000000000..1f967032e3 --- /dev/null +++ b/network/src/main/java/com/tangem/network/common/MoshiConverter.kt @@ -0,0 +1,38 @@ +package com.tangem.network.common + +import com.squareup.moshi.FromJson +import com.squareup.moshi.Moshi +import com.squareup.moshi.ToJson +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.common.json.TangemSdkAdapter +import retrofit2.Converter +import retrofit2.converter.moshi.MoshiConverterFactory +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class MoshiConverter { + + companion object { + fun createFactory(moshi: Moshi = defaultMoshi()): Converter.Factory = MoshiConverterFactory.create(moshi) + + fun defaultMoshi(): Moshi = Moshi.Builder() + .add(BigDecimalAdapter) + .add(KotlinJsonAdapterFactory()) + .add(TangemSdkAdapter.DerivationPathAdapter()) + .add(TangemSdkAdapter.DerivationNodeAdapter()) + .build() + + fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi + } +} + +object BigDecimalAdapter { + @FromJson + fun fromJson(string: String) = BigDecimal(string) + + @ToJson + fun toJson(value: BigDecimal) = value.toString() +} \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/Retrofit.kt b/network/src/main/java/com/tangem/network/common/Retrofit.kt new file mode 100644 index 0000000000..d6e9614290 --- /dev/null +++ b/network/src/main/java/com/tangem/network/common/Retrofit.kt @@ -0,0 +1,41 @@ +package com.tangem.network.common + +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Converter +import retrofit2.Retrofit +import java.util.concurrent.TimeUnit + +// TODO: refactoring: make it better through factory +fun createRetrofitInstance( + baseUrl: String, + okHttpBuilder: OkHttpClient.Builder = OkHttpClient.Builder(), + interceptors: List = emptyList(), + converterFactory: Converter.Factory = MoshiConverter.createFactory(), + logEnabled: Boolean = false +): Retrofit { + interceptors.forEach { okHttpBuilder.addInterceptor(it) } + addTimeOuts(okHttpBuilder) + + if (logEnabled) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor()) + + return Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(converterFactory) + .client(okHttpBuilder.build()) + .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 + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9d495b34f8..eba6dcf2b9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -include ':app' \ No newline at end of file +include ':app' +include ':network'