Updated on 2026-08-14
This commit is contained in:
parent
38276b01ea
commit
9378e4fa31
63 changed files with 1434 additions and 75 deletions
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
// region DI
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.onramp.converters.CountryConverter
|
||||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
|
|
@ -12,13 +14,16 @@ import com.tangem.datasource.api.common.response.getOrThrow
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.TangemExpressValues
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeProvider
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeProviderType
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
|
||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataJson
|
||||
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.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
||||
|
|
@ -28,20 +33,26 @@ import com.tangem.datasource.local.preferences.utils.getObject
|
|||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.joda.time.DateTime
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
internal class DefaultOnrampRepository(
|
||||
private val onrampApi: OnrampApi,
|
||||
private val expressApi: TangemExpressApi,
|
||||
|
|
@ -51,12 +62,16 @@ internal class DefaultOnrampRepository(
|
|||
private val pairsStore: OnrampPairsStore,
|
||||
private val quotesStore: OnrampQuotesStore,
|
||||
private val quotesErrorConverter: OnrampQuotesErrorConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
) : OnrampRepository {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
private val countryConverter = CountryConverter(currencyConverter)
|
||||
private val statusConverter = StatusConverter()
|
||||
private val paymentMethodsConverter = PaymentMethodConverter()
|
||||
private val onrampDataAdapter = moshi.adapter(OnrampDataJson::class.java)
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
|
|
@ -194,14 +209,19 @@ internal class DefaultOnrampRepository(
|
|||
providerId = provider.id,
|
||||
).bind()
|
||||
OnrampQuote.Data(
|
||||
fromAmount = convertToBigDecimal(response.fromAmount),
|
||||
toAmount = convertToBigDecimal(response.toAmount),
|
||||
minFromAmount = convertToBigDecimal(response.minFromAmount),
|
||||
maxFromAmount = convertToBigDecimal(response.maxFromAmount),
|
||||
fromAmount = OnrampAmount(
|
||||
value = response.fromAmount
|
||||
.toBigDecimalOrDefault()
|
||||
.movePointLeft(currency.precision),
|
||||
decimals = currency.precision,
|
||||
symbol = amount.currencySymbol,
|
||||
),
|
||||
toAmount = convertToAmount(response.toAmount, cryptoCurrency),
|
||||
minFromAmount = convertToAmount(response.minFromAmount, cryptoCurrency),
|
||||
maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency),
|
||||
paymentMethod = paymentMethod,
|
||||
provider = provider,
|
||||
)
|
||||
null
|
||||
},
|
||||
onError = { error ->
|
||||
if (error is ApiResponseError.HttpException) {
|
||||
|
|
@ -230,6 +250,69 @@ internal class DefaultOnrampRepository(
|
|||
return quotesStore.get(QUOTES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getQuotesSync(): List<OnrampQuote>? {
|
||||
return quotesStore.getSyncOrNull(QUOTES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getOnrampData(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
quote: OnrampProviderWithQuote.Data,
|
||||
): OnrampTransaction = withContext(dispatchers.io) {
|
||||
try {
|
||||
val address = requireNotNull(
|
||||
value = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network),
|
||||
lazyMessage = { "Address must not be null" },
|
||||
)
|
||||
val fromAmountString = quote.fromAmount.value.movePointRight(quote.fromAmount.decimals).toString()
|
||||
val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" }
|
||||
val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
|
||||
val data = safeApiCall(
|
||||
call = {
|
||||
onrampApi.getData(
|
||||
fromCurrencyCode = currency.code,
|
||||
toContractAddress = cryptoCurrency.getContractAddress(),
|
||||
toNetwork = cryptoCurrency.network.backendId,
|
||||
paymentMethod = quote.paymentMethod.id,
|
||||
countryCode = country.code,
|
||||
fromAmount = fromAmountString,
|
||||
toDecimals = cryptoCurrency.decimals,
|
||||
providerId = quote.provider.id,
|
||||
toAddress = address,
|
||||
redirectUrl = "tangem://redirect?action=dismissBrowser",
|
||||
language = null,
|
||||
theme = null,
|
||||
requestId = UUID.randomUUID().toString(),
|
||||
).bind()
|
||||
},
|
||||
onError = { e ->
|
||||
Timber.e(e)
|
||||
throw e
|
||||
},
|
||||
)
|
||||
if (dataSignatureVerifier.verifySignature(data.signature, data.dataJson)) {
|
||||
val dataJson = requireNotNull(onrampDataAdapter.fromJson(data.dataJson)) {
|
||||
"Can not parse dataJson. ${data.dataJson}"
|
||||
}
|
||||
createOnrampTransaction(
|
||||
txId = data.txId,
|
||||
onrampDataJson = dataJson,
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
provider = quote.provider,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fromAmount = quote.fromAmount.value,
|
||||
toAmount = quote.toAmount.value,
|
||||
)
|
||||
} else {
|
||||
error("Express signature invalid")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPaymentMethods(): List<OnrampPaymentMethod> {
|
||||
val paymentMethods = requireNotNull(paymentMethodsStore.getSyncOrNull(PAYMENT_METHODS_KEY)) {
|
||||
"Onramp payment methods is absent in storage"
|
||||
|
|
@ -237,6 +320,17 @@ internal class DefaultOnrampRepository(
|
|||
return paymentMethodsConverter.convertList(paymentMethods)
|
||||
}
|
||||
|
||||
override suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod) {
|
||||
val dto = paymentMethodsConverter.convertBack(paymentMethod)
|
||||
paymentMethodsStore.store(SELECTED_PAYMENT_METHOD_KEY, listOf(dto))
|
||||
}
|
||||
|
||||
override fun getSelectedPaymentMethod(): Flow<OnrampPaymentMethod> {
|
||||
return paymentMethodsStore
|
||||
.get(SELECTED_PAYMENT_METHOD_KEY)
|
||||
.mapNotNull { paymentMethods -> paymentMethods.firstOrNull()?.let(paymentMethodsConverter::convert) }
|
||||
}
|
||||
|
||||
override suspend fun clearCache() = withContext(NonCancellable) {
|
||||
paymentMethodsStore.clear()
|
||||
pairsStore.clear()
|
||||
|
|
@ -264,18 +358,53 @@ internal class DefaultOnrampRepository(
|
|||
pairsStore.store(PAIRS_KEY, onrampPairs)
|
||||
}
|
||||
|
||||
private fun createOnrampTransaction(
|
||||
txId: String,
|
||||
onrampDataJson: OnrampDataJson,
|
||||
userWalletId: UserWalletId,
|
||||
currency: OnrampCurrency,
|
||||
provider: OnrampProvider,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fromAmount: BigDecimal,
|
||||
toAmount: BigDecimal,
|
||||
): OnrampTransaction {
|
||||
return OnrampTransaction(
|
||||
txId = txId,
|
||||
userWalletId = userWalletId,
|
||||
fromAmount = fromAmount,
|
||||
fromCurrency = currency,
|
||||
toAmount = toAmount,
|
||||
toCurrencyId = cryptoCurrency.id.value,
|
||||
status = OnrampStatus.Status.Expired,
|
||||
externalTxUrl = onrampDataJson.externalTxUrl,
|
||||
externalTxId = onrampDataJson.externalTxId,
|
||||
timestamp = DateTime.now().millis,
|
||||
providerName = provider.info.name,
|
||||
providerImageUrl = provider.info.imageLarge,
|
||||
providerType = ExchangeProviderType.ONRAMP.name,
|
||||
redirectUrl = onrampDataJson.widgetUrl,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getContractAddress(): String = when (this) {
|
||||
is CryptoCurrency.Coin -> TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
is CryptoCurrency.Token -> this.contractAddress
|
||||
}
|
||||
|
||||
private fun convertToBigDecimal(value: String): BigDecimal =
|
||||
requireNotNull(value.toBigDecimalOrNull()) { "Can not parse $value to BigDecimal" }
|
||||
private fun convertToAmount(value: String, cryptoCurrency: CryptoCurrency): OnrampAmount {
|
||||
val cryptoValue = requireNotNull(value.toBigDecimalOrNull()) { "Can not parse $value to BigDecimal" }
|
||||
return OnrampAmount(
|
||||
value = cryptoValue.movePointLeft(cryptoCurrency.decimals),
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<PaymentMethodDTO>.removeApplePay(): List<PaymentMethodDTO> = filterNot { it.id == "apple-pay" }
|
||||
|
||||
private companion object {
|
||||
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
|
||||
const val SELECTED_PAYMENT_METHOD_KEY = "onramp_selected_payment_method"
|
||||
const val PAIRS_KEY = "onramp_pairs"
|
||||
const val QUOTES_KEY = "onramp_quotes"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ package com.tangem.data.onramp.converters
|
|||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class PaymentMethodConverter : Converter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||
internal class PaymentMethodConverter : TwoWayConverter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||
override fun convert(value: PaymentMethodDTO): OnrampPaymentMethod = OnrampPaymentMethod(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
imageUrl = value.image,
|
||||
)
|
||||
|
||||
override fun convertBack(value: OnrampPaymentMethod): PaymentMethodDTO = PaymentMethodDTO(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
image = value.imageUrl,
|
||||
)
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ internal class TransactionConverter : TwoWayConverter<OnrampTransactionDTO, Onra
|
|||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
|
||||
redirectUrl = "", // not necessary field
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ package com.tangem.data.onramp.converters.error
|
|||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.api.express.models.response.ExpressError
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.domain.onramp.model.OnrampAmount
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class OnrampQuotesErrorConverter(
|
||||
|
|
@ -40,7 +39,6 @@ internal class OnrampQuotesErrorConverter(
|
|||
amountWithOffset = minAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
type = input.amount.type,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -59,24 +57,17 @@ internal class OnrampQuotesErrorConverter(
|
|||
amountWithOffset = maxAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
type = input.amount.type,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFromAmountWithOffset(
|
||||
amountWithOffset: String,
|
||||
decimals: Int,
|
||||
symbol: String,
|
||||
type: AmountType,
|
||||
): Amount {
|
||||
return Amount(
|
||||
private fun createFromAmountWithOffset(amountWithOffset: String, decimals: Int, symbol: String): OnrampAmount {
|
||||
return OnrampAmount(
|
||||
value = requireNotNull(
|
||||
amountWithOffset.toBigDecimalOrNull()?.movePointLeft(decimals),
|
||||
) { "wrong amount format, use only digits" },
|
||||
decimals = decimals,
|
||||
currencySymbol = symbol,
|
||||
type = type,
|
||||
symbol = symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.data.onramp.converters.error.OnrampQuotesErrorConverter
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -38,6 +40,9 @@ internal object OnrampDataModule {
|
|||
pairsStore: OnrampPairsStore,
|
||||
quotesStore: OnrampQuotesStore,
|
||||
quotesErrorConverter: OnrampQuotesErrorConverter,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dataSignatureVerifier: DataSignatureVerifier,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): OnrampRepository {
|
||||
return DefaultOnrampRepository(
|
||||
onrampApi = onrampApi,
|
||||
|
|
@ -48,6 +53,9 @@ internal object OnrampDataModule {
|
|||
pairsStore = pairsStore,
|
||||
quotesStore = quotesStore,
|
||||
quotesErrorConverter = quotesErrorConverter,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dataSignatureVerifier = dataSignatureVerifier,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue