From b4ab6e480ec149ba9d6922791cd6944189283a0f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 09:38:35 +0300 Subject: [PATCH 1/6] Updated on 2026-08-14 --- .../com/tangem/tap/common/IntentHandler.kt | 20 ++- .../tangem/tap/common/extensions/String.kt | 31 ++-- .../tap/common/extensions/WalletManager.kt | 20 +-- .../tap/common/redux/global/GlobalAction.kt | 6 +- .../common/redux/global/GlobalMidlleware.kt | 31 ++-- .../tap/common/redux/global/GlobalReducer.kt | 18 +-- .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TapWalletManager.kt | 6 +- .../configurable/config/ConfigManager.kt | 3 + .../domain/configurable/config/JsonModels.kt | 1 + ...ayStatus.kt => CurrencyExchangeManager.kt} | 24 ++- .../tangem/tap/domain/topup/TopUpManager.kt | 46 ------ .../tap/domain/topup/TradeCryptoHelper.kt | 96 ------------ .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../note/redux/OnboardingNoteState.kt | 2 +- .../products/twins/redux/TwinCardsState.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 9 +- .../tap/features/wallet/redux/WalletState.kt | 20 ++- .../middlewares/MultiWalletMiddleware.kt | 5 +- .../middlewares/TradeCryptoMiddleware.kt | 42 +++--- .../redux/reducers/OnWalletLoadedReducer.kt | 10 +- .../wallet/redux/reducers/WalletReducer.kt | 12 +- .../CurrencyExchangeManager.kt | 136 +++++++++++++++++ .../exchangeServices/moonpay/MoonPayApi.kt | 50 ++++++ .../moonpay/MoonPayService.kt | 142 ++++++++++++++++++ .../exchangeServices/onramper/OnnramperApi.kt | 94 ++++++++++++ .../onramper/OnramperService.kt | 111 ++++++++++++++ .../tangem/tap/network/moonpay/MoonpayApi.kt | 23 --- .../tap/network/moonpay/MoonpayService.kt | 90 ----------- 29 files changed, 669 insertions(+), 387 deletions(-) rename app/src/main/java/com/tangem/tap/domain/extensions/{MoonpayStatus.kt => CurrencyExchangeManager.kt} (55%) delete mode 100644 app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index 7058973e1a..1e41053369 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -5,7 +5,6 @@ import android.nfc.NfcAdapter import android.nfc.Tag import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.home.redux.HomeAction @@ -15,6 +14,11 @@ import timber.log.Timber class IntentHandler { + private val TRANSACTION_ID_PARAM = "transactionId" + private val CURRENCY_CODE_PARAM = "baseCurrencyCode" + private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" + private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" + fun handleIntent(intent: Intent?) { handleBackgroundScan(intent) handleWalletConnectLink(intent) @@ -23,8 +27,8 @@ class IntentHandler { private fun handleBackgroundScan(intent: Intent?) { if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || - NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action - ) + NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action + ) ) { val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) if (tag != null) { @@ -45,13 +49,13 @@ class IntentHandler { private fun handleSellCurrencyCallback(intent: Intent?) { try { val transactionID = - intent?.data?.getQueryParameter(TradeCryptoHelper.TRANSACTION_ID_PARAM) ?: return + intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return val currency = - intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_CODE_PARAM) ?: return + intent.data?.getQueryParameter(CURRENCY_CODE_PARAM) ?: return val amount = - intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_AMOUNT_PARAM) ?: return + intent.data?.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return val destinationAddress = - intent.data?.getQueryParameter(TradeCryptoHelper.DEPOSIT_WALLET_ADDRESS_PARAM) + intent.data?.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") @@ -63,7 +67,7 @@ class IntentHandler { transactionId = transactionID )) } catch (exception: Exception) { - Timber.d("Not Moonpay URL") + Timber.d("Not MoonPay URL") } } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index f0f4ef1d00..7d54a7bf92 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.extensions import android.content.Context import android.graphics.Bitmap import android.graphics.Color +import android.net.Uri import android.text.Spannable import android.text.style.ForegroundColorSpan import androidx.core.content.ContextCompat @@ -21,24 +22,24 @@ fun String?.ellipsizeBeforeSpace(allowedSize: Int): String { val startIndex = endIndex - sizeDifference val newString = this.removeRange(startIndex, endIndex) return newString.substring(0 until startIndex) + "..." + - newString.substring(startIndex until newString.length) + newString.substring(startIndex until newString.length) } fun String.colorSegment( - context: Context, - color: Int, - startIndex: Int = 0, - endIndex: Int = this.length + context: Context, + color: Int, + startIndex: Int = 0, + endIndex: Int = this.length ): Spannable { return this.toSpannable() - .also { spannable -> - spannable.setSpan( - ForegroundColorSpan(ContextCompat.getColor(context, color)), - startIndex, - endIndex, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } + .also { spannable -> + spannable.setSpan( + ForegroundColorSpan(ContextCompat.getColor(context, color)), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } } fun String.toQrCode(): Bitmap { @@ -59,4 +60,6 @@ fun String.toQrCode(): Bitmap { } } return bmp -} \ No newline at end of file +} + +fun String.urlEncode(): String = Uri.encode(this) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index f552e835f6..907da9845f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -7,11 +7,11 @@ import com.tangem.common.services.Result import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.features.demo.isDemoWallet import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.reducers.createAddressesData import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.store import timber.log.Timber @@ -44,17 +44,17 @@ suspend fun WalletManager.safeUpdate(): Result = try { } fun WalletManager?.getToUpUrl(): String? { + val globalState = store.state.globalState + val currencyExchangeManager = globalState.currencyExchangeManager ?: return null val wallet = this?.wallet ?: return null - val config = store.state.globalState.configManager?.config ?: return null - val defaultAddress = wallet.address - return TradeCryptoHelper.getUrl( - TradeCryptoHelper.Action.Buy, - wallet.blockchain, - wallet.blockchain.currency, - defaultAddress, - config.moonPayApiKey, - config.moonPayApiSecretKey + val defaultAddress = wallet.address + return currencyExchangeManager.getUrl( + action = CurrencyExchangeManager.Action.Buy, + blockchain = wallet.blockchain, + cryptoCurrencyName = wallet.blockchain.currency, + fiatCurrency = globalState.appCurrency, + walletAddress = defaultAddress, ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 04b8f27743..58684ac334 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -14,7 +14,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse 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.moonpay.MoonpayStatus +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.Action sealed class GlobalAction : Action { @@ -76,7 +76,7 @@ sealed class GlobalAction : Action { data class SendFeedback(val emailData: EmailData) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() - object GetMoonPayStatus : GlobalAction() { - data class Success(val moonPayStatus: MoonpayStatus) : GlobalAction() + object InitCurrencyExchangeManager : GlobalAction() { + data class Success(val exchangeManager: CurrencyExchangeManager) : GlobalAction() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 97fc6ac162..7675e42b03 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError -import com.tangem.common.services.Result +import com.tangem.common.extensions.ifNotNull import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -12,10 +12,11 @@ import com.tangem.tap.common.redux.AppState 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.moonpay.MoonpayService +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService +import com.tangem.tap.network.exchangeServices.onramper.OnramperService import kotlinx.coroutines.launch import org.rekotlin.Middleware -import timber.log.Timber class GlobalMiddleware { companion object { @@ -69,19 +70,21 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.UpdateFeedbackInfo -> { store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + ?.setWalletsInfo(action.walletManagers) } - is GlobalAction.GetMoonPayStatus -> { - val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey - if (apiKey != null) { + is GlobalAction.InitCurrencyExchangeManager -> { + val config = appState()?.globalState?.configManager?.config + ifNotNull( + config?.onramperApiKey, + config?.moonPayApiKey, + config?.moonPayApiSecretKey, + ) { onramperKey, moonPayKey, moonPaySecretKey -> scope.launch { - val result = MoonpayService().getMoonpayStatus(apiKey) - when (result) { - is Result.Success -> { - store.dispatchOnMain(GlobalAction.GetMoonPayStatus.Success(result.data)) - } - is Result.Failure -> Timber.e(result.error) - } + val onramper = OnramperService(onramperKey) + val moonPay = MoonPayService(moonPayKey, moonPaySecretKey) + val exchangeManager = CurrencyExchangeManager(onramper, moonPay) + exchangeManager.getStatus() + store.dispatchOnMain(GlobalAction.InitCurrencyExchangeManager.Success(exchangeManager)) } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 94621117bc..d84c43379d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -2,7 +2,6 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.onboarding.OnboardingManager -import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.tap.preferencesStorage import org.rekotlin.Action @@ -19,7 +18,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.Onboarding.Start -> { val onboardingManager = if (action.scanResponse != null) { val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage - OnboardingManager(action.scanResponse, usedCardsPrefStorage) + OnboardingManager(action.scanResponse, usedCardsPrefStorage) } else { null } @@ -67,19 +66,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.HideDialog -> { globalState.copy(dialog = null) } - is GlobalAction.GetMoonPayStatus.Success -> { - val fiatExchangeIsEnabled = globalState.configManager?.config?.isTopUpEnabled ?: false - val moonpayStatus = if (fiatExchangeIsEnabled) { - action.moonPayStatus - } else { - MoonpayStatus( - isBuyAllowed = false, - isSellAllowed = false, - availableToBuy = emptyList(), - availableToSell = emptyList() - ) - } - globalState.copy(moonpayStatus = moonpayStatus) + is GlobalAction.InitCurrencyExchangeManager.Success -> { + globalState.copy(currencyExchangeManager = action.exchangeManager) } is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index c607125cb8..ace0a06ae7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -11,7 +11,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.feedback.FeedbackManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.coinmarketcap.CoinMarketCapService -import com.tangem.tap.network.moonpay.MoonpayStatus +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType data class GlobalState( @@ -27,7 +27,7 @@ data class GlobalState( val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val moonpayStatus: MoonpayStatus? = null, + val currencyExchangeManager: CurrencyExchangeManager? = null, val resources: AndroidResources = AndroidResources(), val analyticsHandlers: AnalyticsHandler? = null, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 2abf3c060f..55fc7f2154 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -138,8 +138,7 @@ class TapWalletManager { loadMultiWalletData(data, blockchain, null) } } - val moonPayStatus = store.state.globalState.moonpayStatus - store.dispatch(WalletAction.LoadWallet(moonPayStatus)) + store.dispatch(WalletAction.LoadWallet()) store.dispatch(WalletAction.LoadFiatRate()) } } @@ -201,8 +200,7 @@ class TapWalletManager { return@withContext } - val moonPayStatus = store.state.globalState.moonpayStatus - store.dispatch(WalletAction.LoadWallet(moonPayStatus)) + store.dispatch(WalletAction.LoadWallet()) store.dispatch(WalletAction.LoadFiatRate()) } } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 4324891cae..cb587f277b 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -11,6 +11,7 @@ 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 appsFlyerDevKey: String = "", val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), @@ -81,6 +82,7 @@ class ConfigManager( config = config.copy( coinMarketCapKey = values.coinMarketCapKey, moonPayApiKey = values.moonPayApiKey, + onramperApiKey = values.onramperApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, @@ -94,6 +96,7 @@ class ConfigManager( defaultConfig = defaultConfig.copy( coinMarketCapKey = values.coinMarketCapKey, moonPayApiKey = values.moonPayApiKey, + onramperApiKey = values.onramperApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index c82d448e7d..41d1ea2022 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -16,6 +16,7 @@ class FeatureModel( class ConfigValueModel( val coinMarketCapKey: String, val moonPayApiKey: String, + val onramperApiKey: String, val moonPayApiSecretKey: String, val blockchairApiKey: String?, val blockchairAuthorizationToken: String?, diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt similarity index 55% rename from app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt rename to app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt index bd4b7af891..1d39e53861 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt @@ -2,13 +2,22 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.network.moonpay.MoonpayStatus +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import com.tangem.tap.network.exchangeServices.CurrencyExchangeStatus import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean { +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 (!isBuyAllowed) return false @@ -25,16 +34,17 @@ fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean { } } -fun MoonpayStatus.sellIsAllowed(currency: Currency): Boolean { +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 -> { - if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) { - false - } else { - availableToSell.contains(currency.currencySymbol) + 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 diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt deleted file mode 100644 index c5e74334a4..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.domain.topup - -import com.tangem.Message -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.extensions.Result -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TangemSigner -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import java.math.BigDecimal - -class TopUpManager { - suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) { - walletManager.safeUpdate() - - val amountToSend = Amount(walletManager.wallet.blockchain) - val destinationAddress = token.contractAddress - - val feeResult = walletManager.getFee(amountToSend, - destinationAddress) as? Result.Success ?: return - val fee = feeResult.data[0] - - if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) { - return - } - - val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) - - val signer = TangemSigner( - tangemSdk = tangemSdk, Message() - ) { signResponse -> - store.dispatch( - GlobalAction.UpdateWalletSignedHashes( - walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey.seedKey, - remainingSignatures = signResponse.remainingSignatures - ) - ) - } - walletManager.send(transaction, signer) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt deleted file mode 100644 index ec92f9f0fe..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.tangem.tap.domain.topup - -import android.net.Uri -import android.util.Base64 -import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec - -class TradeCryptoHelper { - - companion object { - - const val TRANSACTION_ID_PARAM = "transactionId" - const val CURRENCY_CODE_PARAM = "baseCurrencyCode" - const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" - const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - - private const val REDIRECT_URL_BUY = "tangem://success.tangem.com" - private const val REDIRECT_URL_SELL_REQUEST = "tangem://sell-request.tangem.com" - - private const val BASE_URL_BUY = - "https://buy.moonpay.io" -// "https://buy-staging.moonpay.io" // TESTNET - private const val BASE_URL_SELL = - "https://sell.moonpay.com" -// "https://sell-staging.moonpay.com" // TESTNET - - private const val API_KEY_PATH = "?apiKey=" - private const val CURRENCY_PATH = "¤cyCode=" - private const val WALLET_ADDRESS_PATH = "&walletAddress=" - - private const val REFUND_WALLET_ADDRESS_PATH = "&refundWalletAddress=" - private const val BASE_CURRENCY_PATH = "&baseCurrencyCode=" - - private const val REDIRECT_URL_PATH = "&redirectURL=" - private const val SIGNATURE_PATH = "&signature=" - - private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId=" - - private const val BASE_CURRENCY_USD = "USD" - - fun getUrl( - action: Action, - blockchain: Blockchain?, - cryptoCurrencyName: CryptoCurrencyName, - walletAddress: String, - apiKey: String, - secretKey: String, - ): String { - val originalQuery: String - val baseUrl: String - when (action) { - Action.Buy -> { - if (blockchain?.isTestnet() == true) { - return blockchain.getTestnetTopUpUrl() ?: "" - } - baseUrl = BASE_URL_BUY - originalQuery = API_KEY_PATH + apiKey.urlEncode() + - CURRENCY_PATH + cryptoCurrencyName.urlEncode() + - BASE_CURRENCY_PATH + BASE_CURRENCY_USD.urlEncode() + - WALLET_ADDRESS_PATH + walletAddress.urlEncode() + - REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode() - } - Action.Sell -> { - baseUrl = BASE_URL_SELL - originalQuery = API_KEY_PATH + apiKey.urlEncode() + - BASE_CURRENCY_PATH + cryptoCurrencyName.urlEncode() + - REFUND_WALLET_ADDRESS_PATH + walletAddress.urlEncode() + - REDIRECT_URL_PATH + REDIRECT_URL_SELL_REQUEST.urlEncode() - } - } - val signature = createSignature(originalQuery, secretKey) - return baseUrl + originalQuery + SIGNATURE_PATH + signature.urlEncode() - - } - - fun getSellCryptoReceiptUrl(transactionId: String): String { - return BASE_URL_SELL + TRANSACTION_RECEIPT_PATH + transactionId - } - - private fun String.urlEncode(): String { - return Uri.encode(this) - } - - private fun createSignature(data: String, key: String): String { - val sha256Hmac = Mac.getInstance("HmacSHA256") - val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256") - sha256Hmac.init(secretKey) - val sha256encoded = sha256Hmac.doFinal(data.toByteArray()) - return Base64.encodeToString(sha256encoded, Base64.NO_WRAP) - } - } - - enum class Action { Buy, Sell } -} diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index fd80724118..89c34cbf42 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -38,7 +38,7 @@ private val homeMiddleware: Middleware = { dispatch, state -> when (action) { is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.GetMoonPayStatus) + store.dispatch(GlobalAction.InitCurrencyExchangeManager) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) } is HomeAction.ShouldScanCardOnResume -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt index 150c551f0b..77b12445f7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -27,7 +27,7 @@ data class OnboardingNoteState( get() = steps.indexOf(currentStep) val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> - store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false + store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index 9d68635c08..eb05dd7402 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -57,7 +57,7 @@ data class TwinCardsState( get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> - store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false + store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 029b6b88b5..57a03d4313 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -9,7 +9,6 @@ import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -25,11 +24,7 @@ sealed class WalletAction : Action { } - data class LoadWallet( - val moonpayStatus: MoonpayStatus? = null, - val blockchain: Blockchain? = null, - ) : - WalletAction() { + data class LoadWallet(val blockchain: Blockchain? = null) : WalletAction() { data class Success(val wallet: Wallet) : WalletAction() data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction() data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction() @@ -146,5 +141,5 @@ sealed class WalletAction : Action { data class ChangeSelectedAddress(val type: AddressType) : WalletAction() - data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String): WalletAction() + data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String) : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 4dd980e98c..92491013ad 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -19,7 +19,7 @@ import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.network.moonpay.MoonpayStatus +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.store import org.rekotlin.StateType import java.math.BigDecimal @@ -160,12 +160,18 @@ data class WalletState( return updatedWallets + remainingWallets } - fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletData: WalletData): WalletData { - return walletData.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData)) + fun updateTradeCryptoState( + exchangeManager: CurrencyExchangeManager?, + walletData: WalletData + ): WalletData { + return walletData.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData)) } - fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletDataList: List): List { - return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, it)) } + fun updateTradeCryptoState( + exchangeManager: CurrencyExchangeManager?, + walletDataList: List + ): List { + return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, it)) } } fun addWalletManagers(newWalletManagers: List): WalletState { @@ -226,8 +232,8 @@ data class TradeCryptoState( val buyingAllowed: Boolean = false, ) { companion object { - fun from(moonpayStatus: MoonpayStatus?, walletData: WalletData): TradeCryptoState { - val status = moonpayStatus ?: return walletData.tradeCryptoState + fun from(exchangeManager: CurrencyExchangeManager?, walletData: WalletData): TradeCryptoState { + val status = exchangeManager ?: return walletData.tradeCryptoState val currency = walletData.currency return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 607e12599c..19197ad893 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -63,10 +63,7 @@ class MultiWalletMiddleware { } } store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain))) - store.dispatch(WalletAction.LoadWallet( - moonpayStatus = globalState.moonpayStatus, - blockchain = action.blockchain - ) + store.dispatch(WalletAction.LoadWallet(action.blockchain) ) } is WalletAction.MultiWallet.SaveCurrencies -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index fdf3312ab0..3667a06c35 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -6,17 +6,16 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.topup.TopUpManager -import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.send.redux.PrepareSendScreen 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.scope import com.tangem.tap.store import kotlinx.coroutines.launch -import timber.log.Timber class TradeCryptoMiddleware { @@ -33,44 +32,38 @@ class TradeCryptoMiddleware { private fun startExchange(action: WalletAction.TradeCryptoAction) { val selectedWalletData = store.state.walletState.getSelectedWalletData() - val config = store.state.globalState.configManager?.config ?: return + val exchangeManager = store.state.globalState.currencyExchangeManager ?: return val addresses = selectedWalletData?.walletAddresses ?: return if (addresses.list.isEmpty()) return + val appCurrency = store.state.globalState.appCurrency val defaultAddress = addresses.list[0].address val currency = selectedWalletData.currency - val currencySymbol = selectedWalletData.currency?.currencySymbol ?: return + val currencySymbol = selectedWalletData.currency.currencySymbol val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) { - TradeCryptoHelper.Action.Buy + CurrencyExchangeManager.Action.Buy } else { - TradeCryptoHelper.Action.Sell + CurrencyExchangeManager.Action.Sell } - if (exchangeAction == TradeCryptoHelper.Action.Buy && + if (exchangeAction == CurrencyExchangeManager.Action.Buy && currency is Currency.Token && currency.blockchain.isTestnet() ) { val walletManager = store.state.walletState.getWalletManager(currency.token) if (walletManager !is EthereumWalletManager) return - scope.launch { - TopUpManager().topUpTestErc20Tokens( - walletManager = walletManager, token = currency.token - ) - } + scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) } return } - val url = TradeCryptoHelper.getUrl( + exchangeManager.getUrl( action = exchangeAction, - blockchain = currency?.blockchain, + blockchain = currency.blockchain, cryptoCurrencyName = currencySymbol, - walletAddress = defaultAddress, - apiKey = config.moonPayApiKey, - secretKey = config.moonPayApiSecretKey - ) - Timber.d("Moonpay $exchangeAction URL: $url") - store.dispatchOnMain(NavigationAction.OpenUrl(url)) + fiatCurrency = appCurrency, + walletAddress = defaultAddress)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } + } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { @@ -91,8 +84,11 @@ class TradeCryptoMiddleware { } private fun openReceiptUrl(transactionId: String) { + val exchangeManager = store.state.globalState.currencyExchangeManager ?: return + store.dispatchOnMain(NavigationAction.PopBackTo()) - val url = TradeCryptoHelper.getSellCryptoReceiptUrl(transactionId) - store.dispatchOnMain(NavigationAction.OpenUrl(url)) + exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let { + store.dispatchOnMain(NavigationAction.OpenUrl(it)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 1cad2a84c4..c28bea2b5b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -29,7 +29,7 @@ class OnWalletLoadedReducer { private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { val fiatCurrencySymbol = store.state.globalState.appCurrency - val moonpayStatus = store.state.globalState.moonpayStatus + val exchangeManager = store.state.globalState.currencyExchangeManager val coinAmountValue = wallet.amounts[AmountType.Coin]?.value if (walletState.getWalletData(wallet.blockchain) == null) { @@ -62,7 +62,7 @@ class OnWalletLoadedReducer { pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(coinSendButton), currency = Currency.Blockchain(wallet.blockchain), - tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData) + tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData) ) val tokens = wallet.getTokens().mapNotNull { token -> @@ -86,7 +86,7 @@ class OnWalletLoadedReducer { ), pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(tokenSendButton), - tradeCryptoState = TradeCryptoState.from(moonpayStatus, tokenWalletData) + tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData) ) } val newWallets = (tokens + newWalletData).mapNotNull { it } @@ -106,7 +106,7 @@ class OnWalletLoadedReducer { if (wallet.blockchain != walletState.primaryBlockchain) return walletState val fiatCurrencySymbol = store.state.globalState.appCurrency - val moonpayStatus = store.state.globalState.moonpayStatus + val exchangeManager = store.state.globalState.currencyExchangeManager val token = wallet.getFirstToken() val tokenData = if (token != null) { @@ -154,7 +154,7 @@ class OnWalletLoadedReducer { ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(sendButtonEnabled), - tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletState.primaryWallet) + tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet) ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return walletState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 7005235044..af75afcb1b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -34,7 +34,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { if (action !is WalletAction) return state.walletState - val moonpayStatus = store.state.globalState.moonpayStatus + val exchangeManager = store.state.globalState.currencyExchangeManager var newState = state.walletState when (action) { @@ -103,7 +103,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { currencySymbol = wallet.currencyData.currencySymbol, ), mainButton = WalletMainButton.SendButton(false), - tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) + tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet) ) } newState = newState.copy( @@ -124,11 +124,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState { currencySymbol = wallet.currencyData.currencySymbol, ), mainButton = WalletMainButton.SendButton(false), - tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) + tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet) ) } val wallets = newState.replaceSomeWallets(newWallets) - newState = newState.copy(wallets = newState.updateTradeCryptoState(moonpayStatus, wallets)) + newState = newState.copy(wallets = newState.updateTradeCryptoState(exchangeManager, wallets)) } } is WalletAction.LoadWallet.Success -> newState = @@ -153,7 +153,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = progressState, - wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) + wallets = newState.updateTradeCryptoState(exchangeManager, wallets) ) } is WalletAction.LoadWallet.Failure -> { @@ -188,7 +188,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = progressState, - wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) + wallets = newState.updateTradeCryptoState(exchangeManager, wallets) ) } is WalletAction.SetArtworkId -> { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt new file mode 100644 index 0000000000..a6e9c55cba --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -0,0 +1,136 @@ +package com.tangem.tap.network.exchangeServices + +import com.tangem.Message +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.extensions.Result +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.store +import com.tangem.tap.tangemSdk +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +interface ExchangeService { + suspend fun isBuyAllowed(): Boolean + suspend fun availableToBuy(): List + suspend fun isSellAllowed(): Boolean + suspend fun availableToSell(): List +} + +interface ExchangeUrlBuilder { + fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fiatCurrency: 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, +) : 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 isBuyAllowed(): Boolean = onramperService.isBuyAllowed() + override suspend fun availableToBuy(): List = onramperService.availableToBuy() + override suspend fun isSellAllowed(): Boolean = moonPayService.isSellAllowed() + override suspend fun availableToSell(): List = moonPayService.availableToSell() + + override fun getUrl( + action: Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fiatCurrency: String, + walletAddress: String, + ): String? { + if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl() + + val urlBuilder = getExchangeUrlBuilder(action) + return urlBuilder.getUrl(action, blockchain, cryptoCurrencyName, fiatCurrency, walletAddress) + } + + override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? { + val urlBuilder = getExchangeUrlBuilder(action) + return urlBuilder.getSellCryptoReceiptUrl(action, transactionId) + } + + private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder { + return when (action) { + Action.Buy -> onramperService + Action.Sell -> moonPayService + } as ExchangeUrlBuilder + } + + enum class Action { Buy, Sell } +} + +data class CurrencyExchangeStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean, + val availableToBuy: List, + val availableToSell: List, +) + +suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWalletManager, token: Token) { + walletManager.safeUpdate() + + val amountToSend = Amount(walletManager.wallet.blockchain) + val destinationAddress = token.contractAddress + + val feeResult = walletManager.getFee(amountToSend, + destinationAddress) as? Result.Success ?: return + val fee = feeResult.data[0] + + if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) { + return + } + + val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) + + val signer = TangemSigner( + tangemSdk = tangemSdk, Message() + ) { signResponse -> + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.totalSignedHashes, + walletPublicKey = walletManager.wallet.publicKey.seedKey, + remainingSignatures = signResponse.remainingSignatures + ) + ) + } + walletManager.send(transaction, signer) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt new file mode 100644 index 0000000000..37dbe6185a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.network.exchangeServices.moonpay + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import retrofit2.http.GET +import retrofit2.http.Query + +interface MoonPayApi { + + @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL) + suspend fun getUserStatus( + @Query("apiKey") moonPayApiKey: String, + ): MoonPayUserStatus + + @GET(MOOONPAY_CURRENCIES_REQUEST_URL) + suspend fun getCurrencies( + @Query("apiKey") moonPayApiKey: String, + ): List + + + companion object { + const val MOOONPAY_BASE_URL = "https://api.moonpay.com/" + const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/" + const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/" + } +} + + +@JsonClass(generateAdapter = true) +data class MoonPayUserStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean, + @Json(name = "isAllowed") + val isMoonpayAllowed: Boolean, + @Json(name = "alpha3") + val countryCode: String, + @Json(name = "state") + val stateCode: String, +) + +@JsonClass(generateAdapter = true) +data class MoonPayCurrencies( + val type: String, + val code: String, + val supportsLiveMode: Boolean = false, + val isSuspended: Boolean = true, + val isSupportedInUS: Boolean = false, + val isSellSupported: Boolean = false, + val notAllowedUSStates: List = emptyList(), +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt new file mode 100644 index 0000000000..f3ca64c4db --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -0,0 +1,142 @@ +package com.tangem.tap.network.exchangeServices.moonpay + +import android.net.Uri +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.tap.common.extensions.urlEncode +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.network.createRetrofitInstance +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 + +class MoonPayService( + private val apiKey: String, + private val secretKey: String, +) : ExchangeService, ExchangeUrlBuilder { + + private val api: MoonPayApi by lazy { + createRetrofitInstance(MoonPayApi.MOOONPAY_BASE_URL) + .create(MoonPayApi::class.java) + } + + private var status: MoonPayStatus? = null + + private suspend fun updateStatus() { + try { + coroutineScope { + val userStatusResult = performRequest { api.getUserStatus(apiKey) } + if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult + + val currenciesResult = performRequest { api.getCurrencies(apiKey) } + if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult + + val userStatus = (userStatusResult as Result.Success).data + val currencies = (currenciesResult as Result.Success).data + +// val currenciesToBuy = mutableListOf() + val currenciesToSell = mutableListOf() + + currencies.forEach { currencyStatus -> + if (currencyStatus.type != "crypto" || currencyStatus.isSuspended || + !currencyStatus.supportsLiveMode + ) { + return@forEach + } + + if (userStatus.countryCode == "USA") { + if (!currencyStatus.isSupportedInUS) return@forEach + if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach + } + val currencyCode = currencyStatus.code.uppercase() +// currenciesToBuy.add(currencyCode) + + if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode) + } +// currenciesToBuy.sort() + currenciesToSell.sort() + + status = MoonPayStatus(currenciesToSell, userStatus, currencies) + } + } catch (error: Error) { + status = null + Result.Failure(error) + } + } + + override suspend fun isBuyAllowed(): Boolean = false + + override suspend fun availableToBuy(): List = listOf() + + override suspend fun isSellAllowed(): Boolean { + refreshStatus() + return status?.responseUserStatus?.isSellAllowed ?: false + } + + override suspend fun availableToSell(): List { + refreshStatus() + return status?.availableToSell ?: emptyList() + } + + private suspend fun refreshStatus() { + if (status == null) { + updateStatus() + } + } + + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fatCurrency: String, + walletAddress: String + ): String? { + if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException() + + val uri = Uri.Builder() + .scheme(SCHEME) + .authority(URL_SELL) + .appendQueryParameter("apiKey", apiKey.urlEncode()) + .appendQueryParameter("baseCurrencyCode", cryptoCurrencyName.urlEncode()) + .appendQueryParameter("refundWalletAddress", walletAddress.urlEncode()) + .appendQueryParameter("redirectURL", "tangem://sell-request.tangem.com".urlEncode()) + + val originalQuery = uri.build().toString() + val signature = createSignature(originalQuery) + uri.appendQueryParameter("signature", signature.urlEncode()) + + val url = uri.build().toString() + return url + } + + override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? { + val url = Uri.Builder() + .scheme(SCHEME) + .authority(URL_SELL) + .appendPath("transaction_receipt") + .appendQueryParameter("transactionId", transactionId).build().toString() + return url + + } + + private fun createSignature(data: String): String { + val sha256Hmac = Mac.getInstance("HmacSHA256") + val secretKey = SecretKeySpec(secretKey.toByteArray(), "HmacSHA256") + sha256Hmac.init(secretKey) + val sha256encoded = sha256Hmac.doFinal(data.toByteArray()) + return Base64.encodeToString(sha256encoded, Base64.NO_WRAP) + } +} + +private data class MoonPayStatus( + val availableToSell: List, + val responseUserStatus: MoonPayUserStatus, + val responseCurrencies: List +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt new file mode 100644 index 0000000000..01fec5d3d9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt @@ -0,0 +1,94 @@ +package com.tangem.tap.network.exchangeServices.onramper + +import com.squareup.moshi.JsonClass +import okhttp3.Interceptor +import okhttp3.Response +import retrofit2.http.GET +import retrofit2.http.Path + +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/" + } +} + +class AddKeyToHeaderInterceptor( + private val key: String +) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request().newBuilder().addHeader("Authorization", "Basic $key").build() + return chain.proceed(request) + } +} + +@JsonClass(generateAdapter = true) +data class GatewaysResponse( + val gateways: List +) + +@JsonClass(generateAdapter = true) +data class OnramperGateway( + val identifier: String, + val paymentMethods: List, + val fiatCurrencies: List, + val cryptoCurrencies: List +) + +@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? = 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 +) + +@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 +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt new file mode 100644 index 0000000000..027cdae650 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt @@ -0,0 +1,111 @@ +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.tap.common.extensions.urlEncode +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.network.createRetrofitInstance +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(OnramperApi.BASE_URL, listOf(AddKeyToHeaderInterceptor(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 { + 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 { + refreshStatus() + return status?.availableToBuy ?: emptyList() + } + + private suspend fun refreshStatus() { + if (status == null) { + updateStatus() + } + } + + override suspend fun isSellAllowed(): Boolean = false + + override suspend fun availableToSell(): List = listOf() + + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fatCurrency: 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", fatCurrency) + .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, + val responseGateways: GatewaysResponse +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt deleted file mode 100644 index 5d1bfbb5c2..0000000000 --- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.network.moonpay - -import retrofit2.http.GET -import retrofit2.http.Query - -interface MoonpayApi { - @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL) - suspend fun getUserStatus( - @Query("apiKey") moonpayApiKey: String, - ): MoonPayUserStatus - - @GET(MOOONPAY_CURRENCIES_REQUEST_URL) - suspend fun getCurrencies( - @Query("apiKey") moonpayApiKey: String, - ): List - - - companion object { - const val MOOONPAY_BASE_URL = "https://api.moonpay.com/" - const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/" - const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt deleted file mode 100644 index 3e9696700d..0000000000 --- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.tangem.tap.network.moonpay - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.tap.network.createRetrofitInstance -import kotlinx.coroutines.coroutineScope - -class MoonpayService { - - private val moonpayApi: MoonpayApi by lazy { - createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL) - .create(MoonpayApi::class.java) - } - - suspend fun getMoonpayStatus(moonpayApiKey: String): Result { - return try { - coroutineScope { - val userStatusResult = performRequest { moonpayApi.getUserStatus(moonpayApiKey) } - if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult - - val currenciesResult = performRequest { moonpayApi.getCurrencies(moonpayApiKey) } - if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult - - val userStatus = (userStatusResult as Result.Success).data - val currencies = (currenciesResult as Result.Success).data - - val currenciesToBuy = mutableListOf() - val currenciesToSell = mutableListOf() - - currencies.forEach { currencyStatus -> - if (currencyStatus.type != "crypto" || currencyStatus.isSuspended || - !currencyStatus.supportsLiveMode - ) { - return@forEach - } - - if (userStatus.countryCode == "USA") { - if (!currencyStatus.isSupportedInUS) return@forEach - if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach - } - val currencyCode = currencyStatus.code.uppercase() - currenciesToBuy.add(currencyCode) - - if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode) - } - - Result.Success(MoonpayStatus( - isBuyAllowed = userStatus.isBuyAllowed, - isSellAllowed = userStatus.isSellAllowed, - availableToBuy = if (userStatus.isBuyAllowed) currenciesToBuy else emptyList(), - availableToSell = if (userStatus.isSellAllowed) currenciesToSell else emptyList() - )) - } - } catch (error: Error) { - Result.Failure(error) - } - } -} - -data class MoonpayStatus( - val isBuyAllowed: Boolean, - val isSellAllowed: Boolean, - val availableToBuy: List, - val availableToSell: List, -) - -@JsonClass(generateAdapter = true) -data class MoonPayUserStatus( - val isBuyAllowed: Boolean, - val isSellAllowed: Boolean, - @Json(name = "isAllowed") - val isMoonpayAllowed: Boolean, - @Json(name = "alpha3") - val countryCode: String, - @Json(name = "state") - val stateCode: String, -) - -@JsonClass(generateAdapter = true) -data class MoonpayCurrencies( - val type: String, - val code: String, - val supportsLiveMode: Boolean = false, - val isSuspended: Boolean = true, - val isSupportedInUS: Boolean = false, - val isSellSupported: Boolean = false, - val notAllowedUSStates: List = emptyList(), -) \ No newline at end of file From 14d8fc44ca86bb2ffa066a0a8054a4d86bab7733 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 09:48:16 +0300 Subject: [PATCH 2/6] Updated on 2026-08-14 --- .../tap/network/exchangeServices/onramper/OnramperService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt index 027cdae650..a2534b966d 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt @@ -76,7 +76,7 @@ class OnramperService( action: CurrencyExchangeManager.Action, blockchain: Blockchain, cryptoCurrencyName: CryptoCurrencyName, - fatCurrency: String, + fiatCurrency: String, walletAddress: String, ): String? { var languageCode = Locale.getDefault().language @@ -89,7 +89,7 @@ class OnramperService( .appendQueryParameter("defaultCrypto", cryptoCurrencyName) .appendQueryParameter("wallets", "${blockchain.currency}:$walletAddress".urlEncode()) .appendQueryParameter("redirectURL", SUCCESS_URL) - .appendQueryParameter("defaultFiat", fatCurrency) + .appendQueryParameter("defaultFiat", fiatCurrency) .appendQueryParameter("language", languageCode) status?.apply { From 8b78f82be541b4ffe2b440dd8c75181deafeffaf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 10:48:53 +0300 Subject: [PATCH 3/6] Updated on 2026-08-14 --- .../tangem/tap/common/extensions/Picasso.kt | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt index 0e61ba565c..81e215bcf9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -1,13 +1,12 @@ package com.tangem.tap.common.extensions -import android.graphics.PorterDuff +import android.graphics.* import android.net.Uri -import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.utils.widget.ImageFilterView -import androidx.core.content.ContextCompat import com.squareup.picasso.Callback import com.squareup.picasso.Picasso +import com.squareup.picasso.Transformation import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token @@ -43,6 +42,7 @@ fun Picasso.loadCurrenciesIcon( setTokenImage(imageView, textView, token) } this.load(url) + .transform(RoundedCornersTransform()) .noPlaceholder() ?.into(imageView, object : Callback { @@ -74,19 +74,10 @@ private fun setOfflineCurrencyImage( token: Token?, blockchain: Blockchain, ) { - if (token != null) { - setTokenImage(imageView, textView, token) - } else { - setBlockchainImage(imageView, textView, blockchain) + when (token) { + null -> setBlockchainImage(imageView, textView, blockchain) + else -> setTokenImage(imageView, textView, token) } - if (blockchain.isTestnet()) imageView.tint(R.color.tint) -} - -fun ImageView.tint(colorRes: Int) { -// val color = ContextCompat.getColor(context, colorRes); -// ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color)); - this.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.DARKEN); - } private fun setBlockchainImage( @@ -96,6 +87,7 @@ private fun setBlockchainImage( ) { imageView.setImageResource(blockchain.getIconRes()) imageView.colorFilter = null + if (blockchain.isTestnet()) imageView.saturation = 0f textView.text = null } @@ -112,3 +104,29 @@ private fun setTokenImage( } textView.text = token.symbol.take(1) } + +private class RoundedCornersTransform : Transformation { + + override fun transform(source: Bitmap): Bitmap { + val size = source.width.coerceAtMost(source.height) + val x = (source.width - size) / 2 + val y = (source.height - size) / 2 + val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size) + if (squaredBitmap != source) source.recycle() + + val paint = Paint().apply { + shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + isAntiAlias = true + } + + val rectF = RectF(0f, 0f, source.width.toFloat(), source.height.toFloat()) + val radius = size / 8f + val bitmap = Bitmap.createBitmap(size, size, source.config) + Canvas(bitmap).drawRoundRect(rectF, radius, radius, paint) + squaredBitmap.recycle() + + return bitmap + } + + override fun key(): String = "rounded_corners" +} \ No newline at end of file From f4fa9ac909fe1b2852bb33f5a48c715f52ee0bdd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 11:15:44 +0300 Subject: [PATCH 4/6] Updated on 2026-08-14 --- .../redux/middlewares/WarningsMiddleware.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index b7c104861d..e8ea067b6f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -51,12 +51,12 @@ class WarningsMiddleware { } is WalletAction.Warnings.CheckRemainingSignatures -> { if (action.remainingSignatures != null && - action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { store.state.globalState.warningManager - ?.removeWarnings( - messageRes = R.string.warning_low_signatures_format - ) + ?.removeWarnings( + messageRes = R.string.warning_low_signatures_format + ) addWarningMessage( warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), @@ -100,7 +100,7 @@ class WarningsMiddleware { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } - if (scanResponse.isDemoCard()){ + if (scanResponse.isDemoCard()) { addWarningMessage(WarningMessagesManager.demoCardWarning()) } setWarningMessages() @@ -110,7 +110,7 @@ class WarningsMiddleware { private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { val remainingSignatures = card.remainingSignatures if (remainingSignatures != null && - remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { addWarningMessage( WarningMessagesManager.remainingSignaturesNotEnough( @@ -123,7 +123,7 @@ class WarningsMiddleware { private fun checkIfWarningNeeded( scanResponse: ScanResponse, ): WarningMessage? { - if (scanResponse.isTangemTwins()) return null + if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null if (scanResponse.card.isMultiwalletAllowed) { return if (scanResponse.card.hasSignedHashes()) { @@ -135,7 +135,7 @@ class WarningsMiddleware { } val validator = store.state.walletState.walletManagers.firstOrNull() - as? SignatureCountValidator + as? SignatureCountValidator return if (validator == null) { if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() @@ -160,7 +160,7 @@ class WarningsMiddleware { if (scanResponse.isTangemTwins() || card.isMultiwalletAllowed) return val validator = store.state.walletState.walletManagers.firstOrNull() - as? SignatureCountValidator + as? SignatureCountValidator scope.launch { val signedHashes = card.getSingleWallet()?.totalSignedHashes ?: 0 val result = validator?.validateSignatureCount(signedHashes) From 0893940c2b69778f2e2da81579c4b471e1d77363 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 12:49:06 +0300 Subject: [PATCH 5/6] Updated on 2026-08-14 --- .../tap/features/shop/ui/ShopFragment.kt | 31 ++++++++++++------- app/src/main/res/layout/fragment_shop.xml | 6 ++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 96e2c8619c..51fe6cb54c 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -10,6 +10,7 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction @@ -71,17 +72,25 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } } + private var cardTranslationY = 70f private fun setupCardsImages() { - binding.imvSecond.animate() - .translationY(70f) - .scaleX(0.9f) - .scaleY(0.9f) - .start() - binding.imvThird.animate() - .translationY(140f) - .scaleX(0.8f) - .scaleY(0.8f) - .start() + GlobalLayoutStateHandler(binding.imvSecond).apply { + onStateChanged = { + cardTranslationY = it.height * 0.15f + binding.imvSecond.animate() + .translationY(cardTranslationY) + .scaleX(0.9f) + .scaleY(0.9f) + .start() + binding.imvThird.animate() + .translationY(cardTranslationY * 2) + .scaleX(0.8f) + .scaleY(0.8f) + .start() + detach() + } + } + } private fun setupProductSelection() = with(binding) { @@ -130,7 +139,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } private fun showOrHideThirdCardWithAnimation(show: Boolean) = with(binding) { - val translationY = if (show) 140f else 0f + val translationY = if (show) cardTranslationY * 2 else cardTranslationY if (show) imvThird.show() imvThird.animate() .translationY(translationY) diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml index dbeba6afe6..26cd36c1d7 100644 --- a/app/src/main/res/layout/fragment_shop.xml +++ b/app/src/main/res/layout/fragment_shop.xml @@ -53,10 +53,12 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginStart="36dp" - android:paddingTop="16dp" - android:paddingBottom="44dp" android:layout_marginEnd="36dp" android:layout_weight="1" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingTop="16dp" + android:paddingBottom="44dp" app:layout_constraintBottom_toTopOf="@+id/tv_header" app:layout_constraintTop_toTopOf="parent"> From a046f3621c815db764e72490eb342a5b74a01bb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Mar 2022 12:50:26 +0300 Subject: [PATCH 6/6] Updated on 2026-08-14 --- .../tangem/tap/features/home/compose/HomeButtons.kt | 13 +++++++------ .../com/tangem/tap/features/home/compose/Stories.kt | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt index 1791cf0455..040c72557f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt @@ -25,13 +25,12 @@ fun HomeButtons( Row( horizontalArrangement = Arrangement.SpaceEvenly, - modifier = modifier.fillMaxWidth() + modifier = modifier ) { Button( modifier = Modifier .weight(1f) - .height(42.dp) - , + .height(48.dp), onClick = onScanButtonClick, colors = ButtonDefaults.textButtonColors( backgroundColor = if (isDarkBackground) darkColorBackground else Color.White, @@ -42,14 +41,15 @@ fun HomeButtons( text = stringResource(id = R.string.home_button_scan), fontWeight = FontWeight.Medium, fontSize = 16.sp, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, + maxLines = 1 ) } Spacer(modifier = Modifier.size(8.dp)) Button( modifier = Modifier .weight(1f) - .height(42.dp), + .height(48.dp), onClick = onShopButtonClick, colors = ButtonDefaults.textButtonColors( backgroundColor = if (isDarkBackground) Color.White else darkColorBackground, @@ -60,7 +60,8 @@ fun HomeButtons( text = stringResource(id = R.string.home_button_order), fontWeight = FontWeight.Medium, fontSize = 16.sp, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, + maxLines = 1 ) } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt index c1a434ade6..15728ae713 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt @@ -141,7 +141,7 @@ fun StoriesScreen( isDarkBackground = isDarkBackground, modifier = Modifier .align(Alignment.BottomCenter) - .padding(37.dp) + .padding(16.dp, 0.dp, 16.dp, 37.dp) .fillMaxWidth(), onScanButtonClick = onScanButtonClick, onShopButtonClick = onShopButtonClick