Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-27 14:29:58 +05:00
parent fcd16ea486
commit 550bd29f82
30 changed files with 642 additions and 0 deletions

View file

@ -0,0 +1,66 @@
package com.tangem.datasource.api.onramp
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
interface OnrampApi {
@GET("currencies")
suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>>
@GET("countries")
suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>>
@GET("country-by-ip")
suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO>
@GET("payment-methods")
suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>>
@POST("onramp-pairs")
suspend fun getPairs(@Body body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>>
@GET("onramp-quote")
suspend fun getQuote(
@Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("paymentMethod") paymentMethod: String,
@Query("countryCode") countryCode: String,
@Query("fromAmount") fromAmount: String,
@Query("toDecimals") toDecimals: Int,
@Query("providerId") providerId: String,
): ApiResponse<OnrampQuoteResponse>
@GET("onramp-data")
suspend fun getData(
@Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("paymentMethod") paymentMethod: String,
@Query("countryCode") countryCode: String,
@Query("fromAmount") fromAmount: String,
@Query("toDecimals") toDecimals: Int,
@Query("providerId") providerId: String,
@Query("toAddress") toAddress: String,
@Query("redirectUrl") redirectUrl: String,
@Query("language") language: String?,
@Query("theme") theme: String?,
@Query("requestId") requestId: String,
): ApiResponse<OnrampDataResponse>
@GET("onramp-status")
suspend fun getStatus(@Query("txId") txId: String): ApiResponse<OnrampStatusResponse>
}

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.api.onramp.models.common
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampDestinationDTO(
@Json(name = "contractAddress")
val contractAddress: String,
@Json(name = "network")
val network: String,
)

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.api.onramp.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
@JsonClass(generateAdapter = true)
data class OnrampPairsRequest(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String?,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "to")
val to: List<OnrampDestinationDTO>,
)

View file

@ -0,0 +1,69 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
data class OnrampDataResponseWithTxDetails(
val dataResponse: OnrampDataResponse,
val txDetails: OnrampTxDetails,
)
@JsonClass(generateAdapter = true)
data class OnrampDataResponse(
@Json(name = "txId")
val txId: String,
@Json(name = "dataJson")
val dataJson: String,
@Json(name = "signature")
val signature: String,
)
@JsonClass(generateAdapter = true)
data class OnrampTxDetails(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "providerId")
val providerId: String,
@Json(name = "toAddress")
val toAddress: String,
@Json(name = "redirectUrl")
val redirectUrl: String,
@Json(name = "language")
val language: String?,
@Json(name = "theme")
val theme: String?,
@Json(name = "requestId")
val requestId: String,
@Json(name = "externalTxId")
val externalTxId: String,
@Json(name = "widgetUrl")
val widgetUrl: String,
)

View file

@ -0,0 +1,46 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampQuoteResponse(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toAmount")
val toAmount: String,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "providerId")
val providerId: String,
@Json(name = "minFromAmount")
val minFromAmount: String,
@Json(name = "maxFromAmount")
val maxFromAmount: String,
@Json(name = "minToAmount")
val minToAmount: String,
@Json(name = "maxToAmount")
val maxToAmount: String,
)

View file

@ -0,0 +1,61 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampStatusResponse(
@Json(name = "txId")
val txId: String,
@Json(name = "providerId")
val providerId: String,
@Json(name = "payoutAddress")
val payoutAddress: String,
// @Json(name = "status")
// val status: ???
@Json(name = "failReason")
val failReason: String?,
@Json(name = "externalTxId")
val externalTxId: String,
@Json(name = "externalTxUrl")
val externalTxUrl: String?,
@Json(name = "payoutHash")
val payoutHash: String?,
@Json(name = "createdAt")
val createdAt: String,
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "toDecimals")
val toDecimals: String,
@Json(name = "toAmount")
val toAmount: String,
@Json(name = "toActualAmount")
val toActualAmount: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
)

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampCountryDTO(
@Json(name = "name")
val name: String,
@Json(name = "code")
val code: String,
@Json(name = "image")
val image: String,
@Json(name = "alpha3")
val alpha3: String,
@Json(name = "continent")
val continent: String,
@Json(name = "defaultCurrency")
val defaultCurrency: OnrampCurrencyDTO,
@Json(name = "onrampAvailable")
val onrampAvailable: Boolean,
)

View file

@ -0,0 +1,19 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampCurrencyDTO(
@Json(name = "name")
val name: String,
@Json(name = "code")
val code: String,
@Json(name = "image")
val image: String,
@Json(name = "precision")
val precision: Int,
)

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
@JsonClass(generateAdapter = true)
data class OnrampPairDTO(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String?,
@Json(name = "to")
val to: OnrampDestinationDTO,
@Json(name = "providers")
val providers: List<OnrampProviderDTO>,
)

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampProviderDTO(
@Json(name = "providerId")
val providerId: String,
@Json(name = "paymentMethods")
val paymentMethods: List<String>,
)

View file

@ -0,0 +1,16 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PaymentMethodDTO(
@Json(name = "id")
val id: String,
@Json(name = "name")
val name: String,
@Json(name = "image")
val image: String,
)

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
@ -94,6 +95,27 @@ internal object NetworkModule {
)
}
@Provides
@Singleton
fun provideOnrampApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
apiConfigsManager: ApiConfigsManager,
appLogsStore: AppLogsStore,
): OnrampApi {
return createApi(
id = ApiConfig.ID.Express,
moshi = moshi,
context = context,
apiConfigsManager = apiConfigsManager,
clientBuilder = {
addInterceptor(
NetworkLogsSaveInterceptor(appLogsStore),
)
},
)
}
@Provides
@Singleton
fun provideTangemTechApi(

1
data/onramp/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,50 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.data.onramp"
}
dependencies {
/** Core modules */
implementation(projects.core.datasource)
implementation(projects.core.utils)
/** Common modules */
implementation(projects.data.common)
/** Domain modules */
implementation(projects.domain.onramp)
// region DI
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
// endregion
// region Others dependencies
implementation(deps.androidx.datastore)
implementation(deps.jodatime)
implementation(deps.kotlin.coroutines)
implementation(deps.kotlin.immutable.collections)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.timber)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
implementation(deps.tangem.card.core)
implementation(deps.tangem.blockchain) {
exclude(module = "joda-time")
}
// endregion
}

View file

@ -0,0 +1,19 @@
package com.tangem.data.onramp
import com.tangem.data.onramp.converters.error.OnrampErrorConverter
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.domain.onramp.model.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
internal class DefaultOnrampErrorResolver(
private val onrampErrorConverter: OnrampErrorConverter,
) : OnrampErrorResolver {
override fun resolve(throwable: Throwable): OnrampError {
return if (throwable is ApiResponseError.HttpException) {
onrampErrorConverter.convert(throwable.errorBody.orEmpty())
} else {
OnrampError.UnknownError
}
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.data.onramp
import com.tangem.data.onramp.converters.CountryConverter
import com.tangem.data.onramp.converters.CurrencyConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
internal class DefaultOnrampRepository(
private val onrampApi: OnrampApi,
private val dispatchers: CoroutineDispatcherProvider,
) : OnrampRepository {
private val currencyConverter = CurrencyConverter()
private val countryConverter = CountryConverter(currencyConverter)
override suspend fun fetchCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
return@withContext onrampApi.getCurrencies()
.getOrThrow()
.map(currencyConverter::convert)
}
override suspend fun fetchCountries(): List<OnrampCountry> = withContext(dispatchers.io) {
return@withContext onrampApi.getCountries()
.getOrThrow()
.map(countryConverter::convert)
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.data.onramp.converters
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.utils.converter.Converter
class CountryConverter(
private val currencyConverter: CurrencyConverter,
) : Converter<OnrampCountryDTO, OnrampCountry> {
override fun convert(value: OnrampCountryDTO) = OnrampCountry(
name = value.name,
code = value.code,
image = value.image,
alpha3 = value.alpha3,
continent = value.continent,
defaultCurrency = currencyConverter.convert(value.defaultCurrency),
onrampAvailable = value.onrampAvailable,
)
}

View file

@ -0,0 +1,15 @@
package com.tangem.data.onramp.converters
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.utils.converter.Converter
class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency> {
override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency(
name = value.name,
code = value.code,
image = value.image,
precision = value.precision,
)
}

View file

@ -0,0 +1,11 @@
package com.tangem.data.onramp.converters.error
import com.tangem.domain.onramp.model.OnrampError
import com.tangem.utils.converter.Converter
internal class OnrampErrorConverter : Converter<String, OnrampError> {
override fun convert(value: String): OnrampError {
return OnrampError.UnknownError
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.data.onramp.di
import com.tangem.data.onramp.DefaultOnrampRepository
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object OnrampDataModule {
@Provides
@Singleton
fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository {
return DefaultOnrampRepository(
onrampApi = onrampApi,
dispatchers = dispatchers,
)
}
}

1
domain/onramp/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,24 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
android {
namespace = "com.tangem.domain.onramp"
}
dependencies {
api(projects.domain.onramp.models)
api(projects.domain.core)
implementation(deps.kotlin.serialization)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(deps.tangem.blockchain) {
exclude(module = "joda-time")
}
}

1
domain/onramp/models/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,12 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
dependencies {
implementation(projects.domain.core)
implementation(deps.kotlin.serialization)
implementation(deps.jodatime)
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.onramp.model
data class OnrampCountry(
val name: String,
val code: String,
val image: String,
val alpha3: String,
val continent: String,
val defaultCurrency: OnrampCurrency,
val onrampAvailable: Boolean,
)

View file

@ -0,0 +1,8 @@
package com.tangem.domain.onramp.model
data class OnrampCurrency(
val name: String,
val code: String,
val image: String,
val precision: Int,
)

View file

@ -0,0 +1,5 @@
package com.tangem.domain.onramp.model
sealed class OnrampError {
data object UnknownError : OnrampError()
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.onramp.repositories
import com.tangem.domain.onramp.model.OnrampError
interface OnrampErrorResolver {
fun resolve(throwable: Throwable): OnrampError
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.onramp.repositories
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.OnrampCurrency
interface OnrampRepository {
suspend fun fetchCurrencies(): List<OnrampCurrency>
suspend fun fetchCountries(): List<OnrampCountry>
}

View file

@ -236,6 +236,8 @@ include(":domain:markets")
include(":domain:markets:models")
include(":domain:manage-tokens")
include(":domain:manage-tokens:models")
include(":domain:onramp")
include(":domain:onramp:models")
// endregion Domain modules
// region Data modules
@ -259,4 +261,5 @@ include(":data:staking")
include(":data:wallet-connect")
include(":data:markets")
include(":data:manage-tokens")
include(":data:onramp")
// endregion Data modules