Updated on 2026-08-14
This commit is contained in:
parent
cef51116e6
commit
17066df741
26 changed files with 407 additions and 506 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564
|
||||
Subproject commit feec2950551a1dbabbb018b446b25e17442db21f
|
||||
|
|
@ -49,11 +49,11 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
|||
|
||||
fun WalletManager?.getToUpUrl(): String? {
|
||||
val globalState = store.state.globalState
|
||||
val currencyExchangeManager = globalState.currencyExchangeManager ?: return null
|
||||
val exchangeManager = globalState.exchangeManager ?: return null
|
||||
val wallet = this?.wallet ?: return null
|
||||
|
||||
val defaultAddress = wallet.address
|
||||
return currencyExchangeManager.getUrl(
|
||||
return exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = wallet.blockchain,
|
||||
cryptoCurrencyName = wallet.blockchain.currency,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.feedback.EmailData
|
||||
import com.tangem.tap.features.feedback.FeedbackManager
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
|
@ -77,7 +76,13 @@ sealed class GlobalAction : Action {
|
|||
data class SendFeedback(val emailData: EmailData) : GlobalAction()
|
||||
data class UpdateFeedbackInfo(val walletManagers: List<WalletManager>) : GlobalAction()
|
||||
|
||||
object InitCurrencyExchangeManager : GlobalAction() {
|
||||
data class Success(val exchangeManager: CurrencyExchangeManager) : GlobalAction()
|
||||
object ExchangeManager : GlobalAction() {
|
||||
object Init : GlobalAction() {
|
||||
data class Success(
|
||||
val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager,
|
||||
) : GlobalAction()
|
||||
}
|
||||
|
||||
object Update : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,11 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
|
|
@ -13,9 +15,12 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi
|
||||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.network.exchangeServices.onramper.OnramperService
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class GlobalMiddleware {
|
||||
|
|
@ -24,94 +29,105 @@ class GlobalMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { _, appState ->
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
|
||||
when (action.result) {
|
||||
is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset)
|
||||
handleAction(action, appState, dispatch)
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
|
||||
when (action) {
|
||||
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
|
||||
when (action.result) {
|
||||
is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset)
|
||||
is CompletionResult.Failure -> {
|
||||
if (action.result.error is TangemSdkError.UserCancelled) {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.Increment)
|
||||
if (store.state.globalState.scanCardFailsCounter >= 2) {
|
||||
store.dispatchDialogShow(AppDialog.ScanFailsDialog)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
|
||||
))
|
||||
}
|
||||
is GlobalAction.HideWarningMessage -> {
|
||||
store.state.globalState.warningManager?.let {
|
||||
if (it.hideWarning(action.warning)) {
|
||||
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
|
||||
//TODO: No appropriate warningMessage identification. Make it better later
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.Warnings.Update)
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.SendFeedback -> {
|
||||
store.state.globalState.feedbackManager?.send(action.emailData)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
|
||||
}
|
||||
is GlobalAction.UpdateFeedbackInfo -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init -> {
|
||||
val config = appState()?.globalState?.configManager?.config
|
||||
ifNotNull(
|
||||
config?.mercuryoWidgetId,
|
||||
config?.moonPayApiKey,
|
||||
config?.moonPayApiSecretKey,
|
||||
) { mercuryoWidgetId, moonPayKey, moonPaySecretKey ->
|
||||
scope.launch {
|
||||
val buyService = MercuryoService(MercuryoApi.API_VERSION, mercuryoWidgetId)
|
||||
val sellService = MoonPayService(moonPayKey, moonPaySecretKey)
|
||||
val exchangeManager = CurrencyExchangeManager(buyService, sellService)
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init.Success -> {}
|
||||
is GlobalAction.ExchangeManager.Update -> {
|
||||
val exchangeManager = appState()?.globalState?.exchangeManager.guard {
|
||||
store.dispatchDebugErrorNotification("exchangeManager is not initialized")
|
||||
return
|
||||
}
|
||||
scope.launch { exchangeManager.update() }
|
||||
}
|
||||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
store.state.globalState.analyticsHandlers,
|
||||
currenciesRepository,
|
||||
action.additionalBlockchainsToDerive,
|
||||
action.messageResId
|
||||
)
|
||||
withMainContext {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
|
||||
action.onSuccess?.invoke(result.data)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (action.result.error is TangemSdkError.UserCancelled) {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.Increment)
|
||||
if (store.state.globalState.scanCardFailsCounter >= 2) {
|
||||
store.dispatchDialogShow(AppDialog.ScanFailsDialog)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
|
||||
))
|
||||
}
|
||||
is GlobalAction.HideWarningMessage -> {
|
||||
store.state.globalState.warningManager?.let {
|
||||
if (it.hideWarning(action.warning)) {
|
||||
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
|
||||
//TODO: No appropriate warningMessage identification. Make it better later
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.Warnings.Update)
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is GlobalAction.SendFeedback -> {
|
||||
store.state.globalState.feedbackManager?.send(action.emailData)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
|
||||
}
|
||||
is GlobalAction.UpdateFeedbackInfo -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
}
|
||||
is GlobalAction.InitCurrencyExchangeManager -> {
|
||||
val config = appState()?.globalState?.configManager?.config
|
||||
ifNotNull(
|
||||
config?.onramperApiKey,
|
||||
config?.moonPayApiKey,
|
||||
config?.moonPayApiSecretKey,
|
||||
) { onramperKey, moonPayKey, moonPaySecretKey ->
|
||||
scope.launch {
|
||||
val onramper = OnramperService(onramperKey)
|
||||
val moonPay = MoonPayService(moonPayKey, moonPaySecretKey)
|
||||
val exchangeManager = CurrencyExchangeManager(onramper, moonPay)
|
||||
exchangeManager.getStatus()
|
||||
store.dispatchOnMain(GlobalAction.InitCurrencyExchangeManager.Success(exchangeManager))
|
||||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
store.state.globalState.analyticsHandlers,
|
||||
currenciesRepository,
|
||||
action.additionalBlockchainsToDerive,
|
||||
action.messageResId
|
||||
)
|
||||
withMainContext {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
|
||||
action.onSuccess?.invoke(result.data)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
action.onFailure?.invoke(result.error)
|
||||
}
|
||||
}
|
||||
action.onFailure?.invoke(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,8 +70,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
is GlobalAction.HideDialog -> {
|
||||
globalState.copy(dialog = null)
|
||||
}
|
||||
is GlobalAction.InitCurrencyExchangeManager.Success -> {
|
||||
globalState.copy(currencyExchangeManager = action.exchangeManager)
|
||||
is GlobalAction.ExchangeManager.Init.Success -> {
|
||||
globalState.copy(exchangeManager = action.exchangeManager)
|
||||
}
|
||||
is GlobalAction.SetIfCardVerifiedOnline ->
|
||||
globalState.copy(cardVerifiedOnline = action.verified)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ data class GlobalState(
|
|||
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val currencyExchangeManager: CurrencyExchangeManager? = null,
|
||||
val exchangeManager: CurrencyExchangeManager? = null,
|
||||
val resources: AndroidResources = AndroidResources(),
|
||||
val analyticsHandlers: AnalyticsHandler? = null,
|
||||
) : StateType
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import com.tangem.tap.domain.configurable.Loader
|
|||
data class Config(
|
||||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val onramperApiKey: String = "pk_test_Ix2aCF3ej_5tcDKkBR7MChIvf5Nb0oPORPQ3Oal5G8I0",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val mercuryoWidgetId: String = "",
|
||||
val appsFlyerDevKey: String = "",
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
|
|
@ -82,8 +82,8 @@ class ConfigManager(
|
|||
config = config.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
onramperApiKey = values.onramperApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = values.mercuryoWidgetId,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
|
||||
|
|
@ -96,8 +96,8 @@ class ConfigManager(
|
|||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
onramperApiKey = values.onramperApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = values.mercuryoWidgetId,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class FeatureModel(
|
|||
|
||||
class ConfigValueModel(
|
||||
val coinMarketCapKey: String,
|
||||
val mercuryoWidgetId: String,
|
||||
val moonPayApiKey: String,
|
||||
val onramperApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockchairAuthorizationToken: String?,
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Blockchain.Arbitrum
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeStatus
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun CurrencyExchangeManager.buyIsAllowed(currency: Currency): Boolean {
|
||||
return this.status?.buyIsAllowed(currency) ?: false
|
||||
}
|
||||
|
||||
fun CurrencyExchangeManager.sellIsAllowed(currency: Currency): Boolean {
|
||||
return this.status?.sellIsAllowed(currency) ?: false
|
||||
}
|
||||
|
||||
fun CurrencyExchangeStatus.buyIsAllowed(currency: Currency): Boolean {
|
||||
if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
|
||||
if (currency.blockchain == Arbitrum) return false
|
||||
if (!isBuyAllowed) return false
|
||||
|
||||
//TODO: temporary, for the 3.32 release, unlock all buy button
|
||||
return true
|
||||
|
||||
return when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val blockchain = currency.blockchain
|
||||
when {
|
||||
blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
|
||||
blockchain == Blockchain.Unknown -> false
|
||||
else -> availableToBuy.contains(currency.currencySymbol)
|
||||
}
|
||||
}
|
||||
is Currency.Token -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun CurrencyExchangeStatus.sellIsAllowed(currency: Currency): Boolean {
|
||||
if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
|
||||
if (!isSellAllowed) return false
|
||||
|
||||
return when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val blockchain = currency.blockchain
|
||||
when {
|
||||
blockchain.isTestnet() -> false
|
||||
blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false
|
||||
else -> availableToSell.contains(currency.currencySymbol)
|
||||
}
|
||||
}
|
||||
is Currency.Token -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
when (action) {
|
||||
is HomeAction.Init -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.InitCurrencyExchangeManager)
|
||||
store.dispatch(GlobalAction.ExchangeManager.Init)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
}
|
||||
is HomeAction.ShouldScanCardOnResume -> {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class OnboardingManager(
|
|||
|
||||
data class OnboardingWalletBalance(
|
||||
val value: BigDecimal = BigDecimal.ZERO,
|
||||
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown, null),
|
||||
val currency: Currency = Currency.Blockchain(Blockchain.Unknown, null),
|
||||
val hasIncomingTransaction: Boolean = false,
|
||||
val state: ProgressState,
|
||||
val error: TapError? = null,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.note.redux
|
|||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.buyIsAllowed
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -26,8 +25,8 @@ data class OnboardingNoteState(
|
|||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { thisRef, property ->
|
||||
store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
|
||||
store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.onboarding.products.twins.redux
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.buyIsAllowed
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -56,8 +55,8 @@ data class TwinCardsState(
|
|||
val showAlert: Boolean
|
||||
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { thisRef, property ->
|
||||
store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
|
||||
store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import com.tangem.tap.common.redux.StateDialog
|
|||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.buyIsAllowed
|
||||
import com.tangem.tap.domain.extensions.sellIsAllowed
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
|
@ -62,7 +60,7 @@ data class WalletState(
|
|||
|
||||
val shouldShowDetails: Boolean =
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
|
||||
|
||||
val blockchains: List<Blockchain>
|
||||
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
|
|
@ -363,18 +361,21 @@ data class Artwork(
|
|||
}
|
||||
|
||||
data class TradeCryptoState(
|
||||
val sellingAllowed: Boolean = false,
|
||||
val buyingAllowed: Boolean = false,
|
||||
val isAvailableToSell: () -> Boolean = { false },
|
||||
val isAvailableToBuy: () -> Boolean = { false },
|
||||
) {
|
||||
companion object {
|
||||
fun from(
|
||||
exchangeManager: CurrencyExchangeManager?,
|
||||
walletData: WalletData
|
||||
): TradeCryptoState {
|
||||
val status = exchangeManager ?: return walletData.tradeCryptoState
|
||||
val exchanger = exchangeManager ?: return walletData.tradeCryptoState
|
||||
val currency = walletData.currency
|
||||
|
||||
return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency))
|
||||
return TradeCryptoState(
|
||||
isAvailableToSell = { exchanger.availableForSell(currency) },
|
||||
isAvailableToBuy = { exchanger.availableForBuy(currency) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.tap.features.send.redux.SendAction
|
|||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20Tokens
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -33,7 +33,7 @@ class TradeCryptoMiddleware {
|
|||
|
||||
private fun startExchange(action: WalletAction.TradeCryptoAction) {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData()
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
|
||||
val exchangeManager = store.state.globalState.exchangeManager ?: return
|
||||
val addresses = selectedWalletData?.walletAddresses ?: return
|
||||
if (addresses.list.isEmpty()) return
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
|
|
@ -53,11 +53,11 @@ class TradeCryptoMiddleware {
|
|||
) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency)
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the ETH")
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) }
|
||||
scope.launch { exchangeManager.buyErc20TestnetTokens(walletManager, currency.token) }
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
|
||||
val exchangeManager = store.state.globalState.exchangeManager ?: return
|
||||
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class OnWalletLoadedReducer {
|
|||
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
|
||||
|
||||
val fiatCurrency = store.state.globalState.appCurrency
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
|
||||
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
|
||||
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
|
||||
|
|
@ -117,7 +117,7 @@ class OnWalletLoadedReducer {
|
|||
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
|
||||
|
||||
val fiatCurrencyName = store.state.globalState.appCurrency.code
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
|
||||
val token = wallet.getFirstToken()
|
||||
val tokenData = if (token != null) {
|
||||
|
|
|
|||
|
|
@ -6,34 +6,19 @@ import com.tangem.blockchain.common.Wallet
|
|||
import com.tangem.common.extensions.mapNotNullValues
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toFiatRateString
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.TradeCryptoState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAddresses
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletReducer {
|
||||
companion object {
|
||||
|
|
@ -49,7 +34,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
|
||||
if (action !is WalletAction) return state.walletState
|
||||
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
var newState = state.walletState
|
||||
|
||||
when (action) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.ColorRes
|
||||
|
|
@ -18,25 +14,13 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
|||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.extensions.appendIfNotNull
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.getQRReceiveMessage
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
|
||||
|
|
@ -200,8 +184,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
|
||||
rowButtons.updateButtonsVisibility(
|
||||
buyAllowed = selectedWallet.tradeCryptoState.buyingAllowed,
|
||||
sellAllowed = selectedWallet.tradeCryptoState.sellingAllowed,
|
||||
buyAllowed = selectedWallet.tradeCryptoState.isAvailableToBuy(),
|
||||
sellAllowed = selectedWallet.tradeCryptoState.isAvailableToSell(),
|
||||
sendAllowed = selectedWallet.mainButton.enabled,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,7 @@ import com.tangem.tap.common.extensions.hide
|
|||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.TradeCryptoState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidget
|
||||
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -122,9 +117,8 @@ class SingleWalletView : WalletView {
|
|||
|
||||
setupButtonsType(state, binding)
|
||||
|
||||
val btnConfirm = if (state.tradeCryptoState.sellingAllowed ||
|
||||
state.tradeCryptoState.buyingAllowed
|
||||
) {
|
||||
val tradeState = state.tradeCryptoState
|
||||
val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) {
|
||||
lButtonsShort.btnConfirm
|
||||
} else {
|
||||
lButtonsLong.btnConfirmLong
|
||||
|
|
@ -152,8 +146,8 @@ class SingleWalletView : WalletView {
|
|||
}
|
||||
|
||||
private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) {
|
||||
val allowedToBuy = tradeCryptoState.buyingAllowed
|
||||
val allowedToSell = tradeCryptoState.sellingAllowed
|
||||
val allowedToBuy = tradeCryptoState.isAvailableToBuy()
|
||||
val allowedToSell = tradeCryptoState.isAvailableToSell()
|
||||
val action = when {
|
||||
allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy
|
||||
!allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
|
||||
|
|
@ -180,9 +174,7 @@ class SingleWalletView : WalletView {
|
|||
}
|
||||
|
||||
private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
|
||||
if (state.tradeCryptoState.sellingAllowed ||
|
||||
state.tradeCryptoState.buyingAllowed
|
||||
) {
|
||||
if (state.tradeCryptoState.isAvailableToSell() || state.tradeCryptoState.isAvailableToBuy()) {
|
||||
lButtonsLong.root.hide()
|
||||
lButtonsShort.root.show()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.tap.common.extensions.safeUpdate
|
|||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -18,57 +19,20 @@ import java.math.BigDecimal
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ExchangeService {
|
||||
suspend fun isBuyAllowed(): Boolean
|
||||
suspend fun availableToBuy(): List<String>
|
||||
suspend fun isSellAllowed(): Boolean
|
||||
suspend fun availableToSell(): List<String>
|
||||
}
|
||||
|
||||
interface ExchangeUrlBuilder {
|
||||
fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
): String?
|
||||
|
||||
fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
|
||||
|
||||
companion object {
|
||||
const val SCHEME = "https"
|
||||
const val URL_SELL = "sell.moonpay.com"
|
||||
const val SUCCESS_URL = "tangem://success.tangem.com"
|
||||
}
|
||||
}
|
||||
|
||||
class CurrencyExchangeManager(
|
||||
private val onramperService: ExchangeService,
|
||||
private val moonPayService: ExchangeService,
|
||||
private val buyService: ExchangeService,
|
||||
private val sellService: ExchangeService,
|
||||
) : ExchangeService, ExchangeUrlBuilder {
|
||||
|
||||
var status: CurrencyExchangeStatus? = null
|
||||
private set
|
||||
|
||||
suspend fun getStatus(): CurrencyExchangeStatus {
|
||||
val isBuyAllowed = isBuyAllowed()
|
||||
val isSellAllowed = isSellAllowed()
|
||||
val availableToBuy = availableToBuy()
|
||||
val availableToSell = availableToSell()
|
||||
status = CurrencyExchangeStatus(
|
||||
isBuyAllowed,
|
||||
isSellAllowed,
|
||||
availableToBuy,
|
||||
availableToSell,
|
||||
)
|
||||
return status!!
|
||||
override suspend fun update() {
|
||||
buyService.update()
|
||||
sellService.update()
|
||||
}
|
||||
|
||||
override suspend fun isBuyAllowed(): Boolean = onramperService.isBuyAllowed()
|
||||
override suspend fun availableToBuy(): List<String> = onramperService.availableToBuy()
|
||||
override suspend fun isSellAllowed(): Boolean = moonPayService.isSellAllowed()
|
||||
override suspend fun availableToSell(): List<String> = moonPayService.availableToSell()
|
||||
override fun isBuyAllowed(): Boolean = buyService.isBuyAllowed()
|
||||
override fun isSellAllowed(): Boolean = sellService.isSellAllowed()
|
||||
override fun availableForBuy(currency: Currency): Boolean = buyService.availableForBuy(currency)
|
||||
override fun availableForSell(currency: Currency): Boolean = sellService.availableForSell(currency)
|
||||
|
||||
override fun getUrl(
|
||||
action: Action,
|
||||
|
|
@ -96,22 +60,15 @@ class CurrencyExchangeManager(
|
|||
|
||||
private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder {
|
||||
return when (action) {
|
||||
Action.Buy -> onramperService
|
||||
Action.Sell -> moonPayService
|
||||
Action.Buy -> buyService
|
||||
Action.Sell -> sellService
|
||||
} as ExchangeUrlBuilder
|
||||
}
|
||||
|
||||
enum class Action { Buy, Sell }
|
||||
}
|
||||
|
||||
data class CurrencyExchangeStatus(
|
||||
val isBuyAllowed: Boolean,
|
||||
val isSellAllowed: Boolean,
|
||||
val availableToBuy: List<String>,
|
||||
val availableToSell: List<String>,
|
||||
)
|
||||
|
||||
suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
|
||||
suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(walletManager: EthereumWalletManager, token: Token) {
|
||||
walletManager.safeUpdate()
|
||||
|
||||
val amountToSend = Amount(walletManager.wallet.blockchain)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
interface ExchangeService {
|
||||
suspend fun update()
|
||||
fun isBuyAllowed(): Boolean
|
||||
fun isSellAllowed(): Boolean
|
||||
fun availableForBuy(currency: Currency):Boolean
|
||||
fun availableForSell(currency: Currency):Boolean
|
||||
}
|
||||
|
||||
interface ExchangeUrlBuilder {
|
||||
fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: String,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
): String?
|
||||
|
||||
fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
|
||||
|
||||
companion object {
|
||||
const val SCHEME = "https"
|
||||
const val SUCCESS_URL = "tangem://success.tangem.com"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
|
||||
|
||||
private val CurrenciesUrl = "https://api.mercuryo.io/v1.6/lib/currencies"
|
||||
|
||||
|
||||
|
||||
interface MercuryoApi {
|
||||
|
||||
@GET("{apiVersion}/lib/currencies")
|
||||
suspend fun currencies(
|
||||
@Path("apiVersion") apiVersion: String,
|
||||
): MercuryoCurrenciesResponse
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://api.mercuryo.io/"
|
||||
const val API_VERSION = "v1.6"
|
||||
}
|
||||
}
|
||||
|
||||
data class MercuryoCurrenciesResponse(
|
||||
val status: Int,
|
||||
val data: Data,
|
||||
) {
|
||||
data class Data(
|
||||
val fiat: List<String>,
|
||||
val crypto: List<String>,
|
||||
val config: Config
|
||||
)
|
||||
|
||||
data class Config(
|
||||
val base: Map<String, String>,
|
||||
@Json(name = "has_withdrawal_fee")
|
||||
val hasWithdrawalFee: Map<String, Boolean>,
|
||||
@Json(name = "display_options")
|
||||
val displayOptions: Map<String, DisplayOption>,
|
||||
val icons: Map<String, Any>,
|
||||
)
|
||||
|
||||
data class DisplayOption(
|
||||
@Json(name = "fullname")
|
||||
val fullName: String,
|
||||
@Json(name = "total_digits")
|
||||
val totalDigits: Int,
|
||||
@Json(name = "display_digits")
|
||||
val displayDigits: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
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.createRetrofitInstance
|
||||
import com.tangem.tap.common.extensions.urlEncode
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MercuryoService(
|
||||
private val apiVersion: String,
|
||||
private val mercuryoWidgetId: String,
|
||||
) : ExchangeService, ExchangeUrlBuilder {
|
||||
|
||||
private val api: MercuryoApi = createRetrofitInstance(MercuryoApi.BASE_URL)
|
||||
.create(MercuryoApi::class.java)
|
||||
|
||||
private val blockchainsAvailableToBuy = mutableListOf<Blockchain>()
|
||||
private val tokensAvailableToBy = mutableMapOf<String, MutableList<Blockchain>>()
|
||||
|
||||
override suspend fun update() {
|
||||
when (val result = performRequest { api.currencies(apiVersion) }) {
|
||||
is Result.Success -> {
|
||||
val response = result.data
|
||||
if (response.status == 200) {
|
||||
// all currencies which can be bought
|
||||
val currenciesAvailableToBy = response.data.crypto
|
||||
// tokens which can be bought only from specific blockchain network
|
||||
val supportedTokensWithNetwork = response.data.config.base
|
||||
|
||||
currenciesAvailableToBy.forEach { currencyName ->
|
||||
val blockchain = blockchainFromCurrencyName(currencyName)
|
||||
if (blockchain == null) {
|
||||
// suppose its a token
|
||||
supportedTokensWithNetwork[currencyName]?.let {
|
||||
blockchainFromCurrencyName(it)
|
||||
}?.let { blockchainNetwork ->
|
||||
val supportedInBlockchainsNetwork = tokensAvailableToBy[currencyName]
|
||||
?: mutableListOf()
|
||||
supportedInBlockchainsNetwork.add(blockchainNetwork)
|
||||
tokensAvailableToBy[currencyName] = supportedInBlockchainsNetwork
|
||||
}
|
||||
} else {
|
||||
blockchainsAvailableToBuy.add(blockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
blockchainsAvailableToBuy.clear()
|
||||
tokensAvailableToBy.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isBuyAllowed(): Boolean = true
|
||||
|
||||
override fun isSellAllowed(): Boolean = false
|
||||
|
||||
override fun availableForBuy(currency: Currency): Boolean {
|
||||
if (!isBuyAllowed()) return false
|
||||
|
||||
// blockchains which cant be defined by mercuryo service
|
||||
val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum)
|
||||
val blockchain = currency.blockchain
|
||||
|
||||
return when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
when {
|
||||
blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
|
||||
unsupportedBlockchains.contains(blockchain) -> false
|
||||
else -> {
|
||||
blockchainsAvailableToBuy.contains(currency.blockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val supportedInBlockchains = tokensAvailableToBy[currency.currencySymbol] ?: return false
|
||||
supportedInBlockchains.contains(currency.blockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun availableForSell(currency: Currency): Boolean = false
|
||||
|
||||
override fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String
|
||||
): String? {
|
||||
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
|
||||
|
||||
val builder = Uri.Builder()
|
||||
.scheme(ExchangeUrlBuilder.SCHEME)
|
||||
.authority("exchange.mercuryo.io")
|
||||
.appendQueryParameter("widget_id", mercuryoWidgetId)
|
||||
.appendQueryParameter("type", action.name.lowercase())
|
||||
.appendQueryParameter("currency", cryptoCurrencyName)
|
||||
.appendQueryParameter("address", walletAddress.urlEncode())
|
||||
.appendQueryParameter("fix_currency", "true")
|
||||
.appendQueryParameter("return_url", ExchangeUrlBuilder.SUCCESS_URL)
|
||||
|
||||
val url = builder.build().toString()
|
||||
return url
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
transactionId: String
|
||||
): String? = null
|
||||
|
||||
private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
|
||||
"BNB" -> Blockchain.BSC
|
||||
"ETH" -> Blockchain.Ethereum
|
||||
else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,15 @@ import android.util.Base64
|
|||
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.tap.common.extensions.urlEncode
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.URL_SELL
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
|
@ -29,14 +29,14 @@ class MoonPayService(
|
|||
|
||||
private var status: MoonPayStatus? = null
|
||||
|
||||
private suspend fun updateStatus() {
|
||||
try {
|
||||
coroutineScope {
|
||||
override suspend fun update() {
|
||||
withIOContext {
|
||||
performRequest {
|
||||
val userStatusResult = performRequest { api.getUserStatus(apiKey) }
|
||||
if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult
|
||||
if (userStatusResult is Result.Failure) return@performRequest
|
||||
|
||||
val currenciesResult = performRequest { api.getCurrencies(apiKey) }
|
||||
if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult
|
||||
if (currenciesResult is Result.Failure) return@performRequest
|
||||
|
||||
val userStatus = (userStatusResult as Result.Success).data
|
||||
val currencies = (currenciesResult as Result.Success).data
|
||||
|
|
@ -65,29 +65,31 @@ class MoonPayService(
|
|||
|
||||
status = MoonPayStatus(currenciesToSell, userStatus, currencies)
|
||||
}
|
||||
} catch (error: Error) {
|
||||
status = null
|
||||
Result.Failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isBuyAllowed(): Boolean = false
|
||||
override fun isBuyAllowed(): Boolean = false
|
||||
|
||||
override suspend fun availableToBuy(): List<String> = listOf()
|
||||
|
||||
override suspend fun isSellAllowed(): Boolean {
|
||||
refreshStatus()
|
||||
override fun isSellAllowed(): Boolean {
|
||||
return status?.responseUserStatus?.isSellAllowed ?: false
|
||||
}
|
||||
|
||||
override suspend fun availableToSell(): List<String> {
|
||||
refreshStatus()
|
||||
return status?.availableToSell ?: emptyList()
|
||||
}
|
||||
override fun availableForBuy(currency: Currency): Boolean = false
|
||||
|
||||
private suspend fun refreshStatus() {
|
||||
if (status == null) {
|
||||
updateStatus()
|
||||
override fun availableForSell(currency: Currency): Boolean {
|
||||
val availableForSell = status?.availableForSell ?: return false
|
||||
if (!isSellAllowed()) return false
|
||||
|
||||
return when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val blockchain = currency.blockchain
|
||||
when {
|
||||
blockchain.isTestnet() -> false
|
||||
blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false
|
||||
else -> availableForSell.contains(currency.currencySymbol)
|
||||
}
|
||||
}
|
||||
is Currency.Token -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +135,14 @@ class MoonPayService(
|
|||
val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
|
||||
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val URL_SELL = "sell.moonpay.com"
|
||||
}
|
||||
}
|
||||
|
||||
private data class MoonPayStatus(
|
||||
val availableToSell: List<String>,
|
||||
val availableForSell: List<String>,
|
||||
val responseUserStatus: MoonPayUserStatus,
|
||||
val responseCurrencies: List<MoonPayCurrencies>
|
||||
)
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices.onramper
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface OnramperApi {
|
||||
@GET("gateways")
|
||||
suspend fun gateways(): GatewaysResponse
|
||||
|
||||
@GET("rate/{fromCurrency}/{toCurrency}/{paymentMethod}/{amount}")
|
||||
suspend fun rate(
|
||||
@Path("fromCurrency") fromCurrency: String,
|
||||
@Path("toCurrency") toCurrency: String,
|
||||
@Path("paymentMethod") paymentMethod: String,
|
||||
@Path("amount") amount: Int,
|
||||
): RateResponse
|
||||
|
||||
companion object {
|
||||
val BASE_URL = "https://onramper.tech/"
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GatewaysResponse(
|
||||
val gateways: List<OnramperGateway>
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperGateway(
|
||||
val identifier: String,
|
||||
val paymentMethods: List<String>,
|
||||
val fiatCurrencies: List<OnramperCurrency>,
|
||||
val cryptoCurrencies: List<OnramperCurrency>
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperCurrency(
|
||||
val id: String,
|
||||
val code: String,
|
||||
val precision: Int
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RateResponse(
|
||||
val identifier: String,
|
||||
val duration: OnramperDuration,
|
||||
val available: Boolean,
|
||||
val error: OnramperError? = null,
|
||||
val rate: Double? = null,
|
||||
val fees: Double? = null,
|
||||
val requiredKYC: List<String>? = null,
|
||||
val receivedCrypto: Double? = null,
|
||||
val nextStep: OnramperNextStep? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperNextStep(
|
||||
val type: String,
|
||||
val url: String,
|
||||
val message: String,
|
||||
val extraData: List<OnramperExtraData>
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperExtraData(
|
||||
val type: String,
|
||||
val name: String,
|
||||
val humanName: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperDuration(
|
||||
val seconds: Long,
|
||||
val message: String
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnramperError(
|
||||
val type: String,
|
||||
val message: String,
|
||||
val limit: Double
|
||||
)
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices.onramper
|
||||
|
||||
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
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SUCCESS_URL
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnramperService(
|
||||
val apiKey: String
|
||||
) : ExchangeService, ExchangeUrlBuilder {
|
||||
|
||||
private val api: OnramperApi by lazy {
|
||||
createRetrofitInstance(
|
||||
baseUrl = OnramperApi.BASE_URL,
|
||||
interceptors = listOf(
|
||||
AddHeaderInterceptor(mapOf("Authorization" to "Basic $apiKey")),
|
||||
)
|
||||
).create(OnramperApi::class.java)
|
||||
}
|
||||
|
||||
private var status: OnramperStatus? = null
|
||||
|
||||
private suspend fun updateStatus() {
|
||||
try {
|
||||
coroutineScope {
|
||||
val result = performRequest { api.gateways() }
|
||||
if (result is Result.Failure) return@coroutineScope result
|
||||
|
||||
val response = (result as Result.Success).data
|
||||
val currenciesToBuy = extractCurrenciesToBuy(response).sorted()
|
||||
val status = OnramperStatus(currenciesToBuy, response)
|
||||
this@OnramperService.status = status
|
||||
}
|
||||
} catch (error: Error) {
|
||||
status = null
|
||||
Result.Failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractCurrenciesToBuy(response: GatewaysResponse): List<String> {
|
||||
return response.gateways.map { gateway ->
|
||||
gateway.cryptoCurrencies.map { currency -> currency.code }
|
||||
}.flatten().toMutableSet().toList()
|
||||
}
|
||||
|
||||
override suspend fun isBuyAllowed(): Boolean {
|
||||
refreshStatus()
|
||||
return status != null
|
||||
}
|
||||
|
||||
override suspend fun availableToBuy(): List<String> {
|
||||
refreshStatus()
|
||||
return status?.availableToBuy ?: emptyList()
|
||||
}
|
||||
|
||||
private suspend fun refreshStatus() {
|
||||
if (status == null) {
|
||||
updateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isSellAllowed(): Boolean = false
|
||||
|
||||
override suspend fun availableToSell(): List<String> = listOf()
|
||||
|
||||
override fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
fiatCurrency: String,
|
||||
walletAddress: String,
|
||||
): String? {
|
||||
var languageCode = Locale.getDefault().language
|
||||
if (languageCode.isEmpty()) languageCode = "en"
|
||||
|
||||
val builder = Uri.Builder()
|
||||
.scheme(SCHEME)
|
||||
.authority("widget.onramper.com")
|
||||
.appendQueryParameter("apiKey", this.apiKey.urlEncode())
|
||||
.appendQueryParameter("defaultCrypto", cryptoCurrencyName)
|
||||
.appendQueryParameter("wallets", "${blockchain.currency}:$walletAddress".urlEncode())
|
||||
.appendQueryParameter("redirectURL", SUCCESS_URL)
|
||||
.appendQueryParameter("defaultFiat", fiatCurrency)
|
||||
.appendQueryParameter("language", languageCode)
|
||||
|
||||
status?.apply {
|
||||
val gateways = responseGateways.gateways.joinToString(",") { it.identifier }.urlEncode()
|
||||
builder.appendQueryParameter("onlyGateways", gateways)
|
||||
|
||||
}
|
||||
|
||||
val url = builder.build().toString()
|
||||
return url
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
|
||||
}
|
||||
|
||||
private data class OnramperStatus(
|
||||
val availableToBuy: List<String>,
|
||||
val responseGateways: GatewaysResponse
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue