Updated on 2026-08-14

This commit is contained in:
Tangem 2022-07-29 19:55:06 +03:00
commit 76b6721fff
58 changed files with 1198 additions and 681 deletions

View file

@ -93,7 +93,7 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:develop-93'
implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97'
// implementation 'com.tangem:blockchain:0.0.1'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'

@ -1 +1 @@
Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564
Subproject commit bf673f7f88ed561cd8827447c4886f80f30b7e23

View file

@ -22,6 +22,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedB
import com.tangem.tap.features.wallet.redux.WalletDialog
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog
import com.tangem.tap.features.wallet.ui.dialogs.RussianCardholdersWarningBottomSheetDialog
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
@ -115,6 +116,8 @@ class DialogManager : StoreSubscriber<GlobalState> {
AmountToSendBottomSheetDialog(context, state.dialog)
is WalletDialog.SignedHashesMultiWalletDialog ->
SignedHashesWarningDialog.create(context)
is WalletDialog.RussianCardholdersWarningDialog ->
RussianCardholdersWarningBottomSheetDialog(context)
else -> null
}
dialog?.show()

View file

@ -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,

View file

@ -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,17 @@ 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()
}
object FetchUserCountry : GlobalAction() {
data class Success(val countryCode: String) : GlobalAction()
}
}

View file

@ -2,20 +2,31 @@ 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.common.services.Result
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
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.currenciesRepository
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 com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import java.util.Locale
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
class GlobalMiddleware {
@ -24,94 +35,131 @@ 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?.mercuryoSecret,
config?.moonPayApiKey,
config?.moonPayApiSecretKey,
) { mercuryoWidgetId, mercuryoSecret, moonPayKey, moonPaySecretKey ->
scope.launch {
val buyService = MercuryoService(
apiVersion = MercuryoApi.API_VERSION,
mercuryoWidgetId = mercuryoWidgetId,
secret = mercuryoSecret,
)
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)
}
is GlobalAction.FetchUserCountry -> {
scope.launch {
val techService = store.state.domainNetworks.tangemTechService
when (val result = techService.userCountry()) {
is Result.Success -> {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
countryCode = result.data.code.lowercase()
)
)
}
is Result.Failure -> {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
countryCode = Locale.getDefault().country.lowercase()
)
)
}
}
}
}
}
}

View file

@ -70,12 +70,14 @@ 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)
is GlobalAction.FetchUserCountry.Success -> globalState.copy(
userCountryCode = action.countryCode
)
else -> globalState
}
}

View file

@ -25,9 +25,10 @@ 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,
val userCountryCode: String? = null,
) : StateType

View file

@ -11,8 +11,9 @@ 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 mercuryoSecret: String = "",
val appsFlyerDevKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
@ -82,13 +83,15 @@ class ConfigManager(
config = config.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
mercuryoWidgetId = values.mercuryoWidgetId,
mercuryoSecret = values.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
blockcypherTokens = values.blockcypherTokens,
infuraProjectId = values.infuraProjectId
infuraProjectId = values.infuraProjectId,
tronGridApiKey = values.tronGridApiKey
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
@ -96,8 +99,9 @@ class ConfigManager(
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
mercuryoWidgetId = values.mercuryoWidgetId,
mercuryoSecret = values.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
blockchairAuthorizationToken = values.blockchairAuthorizationToken,

View file

@ -15,15 +15,17 @@ class FeatureModel(
class ConfigValueModel(
val coinMarketCapKey: String,
val mercuryoWidgetId: String,
val mercuryoSecret: String,
val moonPayApiKey: String,
val onramperApiKey: String,
val moonPayApiSecretKey: String,
val blockchairApiKey: String?,
val blockchairAuthorizationToken: String?,
val blockcypherTokens: Set<String>?,
val infuraProjectId: String?,
val appsFlyerDevKey: String,
val shopifyShop: ShopifyShop?
val shopifyShop: ShopifyShop?,
val tronGridApiKey: String,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {

View file

@ -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
}
}

View file

@ -104,56 +104,25 @@ class DemoConfig {
?: Amount(BigDecimal.ZERO, blockchain).copy()
private fun getReleaseIds(): List<String> {
return (releaseDemoCardIds +
releaseDemoCardIds_19042022 +
testDemoCardIds).distinct()
return (releaseDemoCardIds + testDemoCardIds).distinct()
}
private val releaseDemoCardIds = mutableListOf<String>(
// Tangem Wallet:
"AC01000000041100",
"AC01000000042462",
"AC01000000041647",
"AC01000000041621",
"AC01000000041217",
private val releaseDemoCardIds = mutableListOf(
// === Not from the Google Sheet table ===
"FB10000000000196", // Note BTC
"FB20000000000186", // Note ETH
"FB30000000000176", // Wallet
"AC01000000041225",
"AC01000000041209",
"AC01000000041092",
"AC01000000041472",
"AC01000000041662",
"AC01000000045754",
"AC01000000045960",
// Tangem Note BTC:
"AB01000000046530",
"AB01000000046720",
"AB01000000046746",
"AB01000000046498",
"AB01000000046753",
"AB01000000049608",
"AB01000000046761",
"AB01000000049574",
"AB01000000046605",
"AB01000000046571",
"AB01000000046704",
"AB01000000046647",
// Tangem Note Ethereum:
"AB02000000051000",
"AB02000000050986",
"AB02000000051026",
"AB02000000051042",
"AB02000000051091",
"AB02000000051083",
"AB02000000050960",
"AB02000000051034",
"AB02000000050911",
"AB02000000051133",
"AB02000000051158",
"AB02000000051059",
)
// https://tangem.slack.com/archives/GMXC6PP71/p1650360610759269
private val releaseDemoCardIds_19042022 = listOf(
// Wallet
// === Mvideo ===
// Wallet
"AC01000000045754",
"AC01000000041662",
"AC01000000041647",
@ -186,7 +155,58 @@ class DemoConfig {
"AC01000000013497",
"AC01000000013836",
"AC01000000013505",
// Note BTC
"AC03000000046693",
"AC03000000046685",
"AC03000000046677",
"AC03000000046669",
"AC03000000046651",
"AC03000000046644",
"AC03000000046636",
"AC03000000046628",
"AC03000000046610",
"AC03000000046602",
"AC03000000046594",
"AC03000000046586",
"AC03000000046578",
"AC03000000046560",
"AC03000000046552",
"AC03000000046545",
"AC03000000046537",
"AC03000000046529",
"AC03000000046511",
"AC03000000046800",
"AC03000000046792",
"AC03000000046784",
"AC03000000046776",
"AC03000000046768",
"AC03000000046750",
"AC03000000046743",
"AC03000000046735",
"AC03000000046727",
"AC03000000046446",
"AC03000000046438",
"AC03000000046412",
"AC03000000046388",
"AC03000000046370",
"AC03000000046354",
"AC03000000046347",
"AC03000000046339",
"AC03000000046321",
"AC03000000046172",
"AC03000000046396",
"AC03000000046404",
"AC03000000046701",
"AC03000000046420",
"AC03000000046719",
"AC03000000046503",
"AC03000000046495",
"AC03000000046487",
"AC03000000046362",
"AC03000000046479",
"AC03000000046461",
"AC03000000046453",
// Note BTC
"AB01000000059608",
"AB01000000046647",
"AB01000000046571",
@ -219,7 +239,58 @@ class DemoConfig {
"AB01000000015782",
"AB01000000022598",
"AB01000000022580",
// Note ETH
"AB01000000005688",
"AB07000000005696",
"AB07000000005902",
"AB07000000005910",
"AB07000000005928",
"AB07000000005936",
"AB07000000005944",
"AB07000000005993",
"AB07000000005985",
"AB07000000005977",
"AB07000000005969",
"AB07000000005951",
"AB07000000005605",
"AB07000000005803",
"AB07000000005811",
"AB07000000005829",
"AB07000000005837",
"AB07000000005845",
"AB07000000005852",
"AB07000000005860",
"AB07000000005878",
"AB07000000005886",
"AB07000000005894",
"AB07000000005704",
"AB07000000005712",
"AB07000000005720",
"AB07000000005738",
"AB07000000005746",
"AB07000000005514",
"AB07000000005522",
"AB07000000005563",
"AB07000000005571",
"AB07000000005589",
"AB07000000005597",
"AB07000000005613",
"AB07000000005621",
"AB07000000005639",
"AB07000000005647",
"AB07000000005654",
"AB07000000005662",
"AB07000000005670",
"AB07000000005530",
"AB07000000005548",
"AB07000000005555",
"AB07000000005753",
"AB07000000005761",
"AB07000000005779",
"AB07000000005787",
"AB07000000005795",
"AB07000000005506",
// Note ETH
"AB02000000051083",
"AB02000000051059",
"AB02000000051158",
@ -252,6 +323,123 @@ class DemoConfig {
"AB02000000020252",
"AB02000000018652",
"AB02000000018561",
"AB08000000009481",
"AB08000000009473",
"AB08000000009705",
"AB08000000009897",
"AB08000000009689",
"AB08000000009671",
"AB08000000009465",
"AB08000000009457",
"AB08000000009440",
"AB08000000009432",
"AB08000000009424",
"AB08000000009416",
"AB08000000009408",
"AB08000000009390",
"AB08000000009374",
"AB08000000009382",
"AB08000000009267",
"AB08000000009275",
"AB08000000009283",
"AB08000000009291",
"AB08000000009309",
"AB08000000009317",
"AB08000000009325",
"AB08000000009333",
"AB08000000009341",
"AB08000000009358",
"AB08000000009366",
"AB08000000009077",
"AB08000000009143",
"AB08000000009168",
"AB08000000009184",
"AB08000000009192",
"AB08000000009200",
"AB08000000009226",
"AB08000000009218",
"AB08000000009234",
"AB08000000009242",
"AB08000000008574",
"AB08000000009069",
"AB08000000008525",
"AB08000000009051",
"AB08000000009135",
"AB08000000009150",
"AB08000000009176",
"AB08000000009085",
"AB08000000009093",
"AB08000000009101",
"AB08000000009119",
"AB08000000009127",
"AB08000000009259",
// === Technopark ===
// Wallet
"AC01000000044120",
"AC01000000044997",
"AC01000000044989",
"AC01000000043494",
"AC01000000043486",
"AC01000000044187",
"AC01000000043148",
"AC01000000044013",
"AC01000000043973",
"AC01000000044815",
"AC01000000044807",
"AC01000000043809",
"AC01000000043833",
"AC01000000043460",
"AC01000000043064",
"AC01000000044138",
"AC01000000044500",
"AC01000000044492",
"AC01000000044260",
"AC01000000044278",
// Note BTC
"AB01000000049864",
"AB01000000053239",
"AB01000000053056",
"AB01000000054237",
"AB01000000054245",
"AB01000000054211",
"AB01000000054229",
"AB01000000053189",
"AB01000000054195",
"AB01000000050797",
"AB01000000053833",
"AB01000000052124",
"AB01000000051605",
"AB01000000052223",
"AB01000000052207",
"AB01000000052199",
"AB01000000047785",
"AB01000000047850",
"AB01000000047868",
"AB01000000048288",
// Note ETH
"AB02000000049715",
"AB02000000049848",
"AB02000000049814",
"AB02000000049863",
"AB02000000049871",
"AB02000000049855",
"AB02000000049285",
"AB02000000049277",
"AB02000000049558",
"AB02000000049889",
"AB02000000049988",
"AB02000000049707",
"AB02000000049699",
"AB02000000049897",
"AB02000000049905",
"AB02000000049913",
"AB02000000049251",
"AB02000000049533",
"AB02000000049541",
"AB02000000049830",
)
private val testDemoCardIds = listOf(
@ -281,7 +469,6 @@ class DemoTransactionSender(
}
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)

View file

@ -38,7 +38,9 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
StoriesScreen(
homeState,
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) },
onShopButtonClick = {
store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode))
},
onSearchTokensClick = {
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
store.dispatch(TokensAction.AllowToAddTokens(false))

View file

@ -44,5 +44,8 @@ class TelephonyManagerRegionProvider(context: Context) : RegionProvider {
}
class LocaleRegionProvider : RegionProvider {
override fun getRegion(): String? = Locale.current.region
}
override fun getRegion(): String = Locale.current.region
}
const val RUSSIA_COUNTRY_CODE = "ru"
const val BELARUS_COUNTRY_CODE = "by"

View file

@ -1,13 +1,12 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.features.home.RegionProvider
import org.rekotlin.Action
sealed class HomeAction : Action {
// from ui
object ReadCard : HomeAction()
data class GoToShop(val regionProvider: RegionProvider) : HomeAction()
data class GoToShop(val userCountryCode: String?) : HomeAction()
// internal
data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()

View file

@ -15,6 +15,8 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@ -30,19 +32,19 @@ class HomeMiddleware {
companion object {
val handler = homeMiddleware
const val CARD_SHOP_URI = "http://cards.tangem.com/"
const val BUY_WALLET_URL = "https://mv.tangem.com/"
const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/"
}
}
private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
private val homeMiddleware: Middleware<AppState> = { _, _ ->
{ next ->
{ action ->
when (action) {
is HomeAction.Init -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.InitCurrencyExchangeManager)
store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
store.dispatch(GlobalAction.FetchUserCountry)
}
is HomeAction.ShouldScanCardOnResume -> {
if (action.shouldScanCard) {
@ -55,8 +57,9 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
// store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomTokens))
}
is HomeAction.GoToShop -> {
when (action.regionProvider.getRegion()?.toLowerCase()) {
"ru" -> store.dispatchOpenUrl(BUY_WALLET_URL)
when (action.userCountryCode) {
RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE ->
store.dispatchOpenUrl(BUY_WALLET_URL)
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
}
store.state.globalState.analyticsHandlers?.triggerEvent(

View file

@ -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,

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -10,8 +10,8 @@ data class PendingTransaction(
val type: PendingTransactionType,
) {
val address: String? = when (type) {
PendingTransactionType.Incoming -> transactionData.destinationAddress
PendingTransactionType.Outgoing -> transactionData.sourceAddress
PendingTransactionType.Incoming -> transactionData.sourceAddress
PendingTransactionType.Outgoing -> transactionData.destinationAddress
PendingTransactionType.Unknown -> null
}

View file

@ -4,7 +4,6 @@ sealed class WalletWarning(
val showingPosition: Int,
) {
object TransactionInProgress : WalletWarning(10)
object SolanaTokensUnsupported : WalletWarning(20)
data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30)
data class Rent(val walletRent: WalletRent) : WalletWarning(40)
}

View file

@ -145,6 +145,7 @@ sealed class WalletAction : Action {
object SignedHashesMultiWalletDialog : DialogAction()
object ChooseTradeActionDialog : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
object RussianCardholdersWarningDialog : DialogAction()
object Hide : DialogAction()
}
@ -155,8 +156,10 @@ sealed class WalletAction : Action {
object EmptyWallet : WalletAction()
sealed class TradeCryptoAction : WalletAction() {
object Buy : TradeCryptoAction()
object Sell : TradeCryptoAction()
data class Buy(
val checkUserLocation: Boolean = true,
) : TradeCryptoAction()
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
data class SendCrypto(
val currencyId: String,

View file

@ -1,10 +1,14 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.entities.Button
@ -14,21 +18,25 @@ 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
import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.models.hasSendableAmounts
import com.tangem.tap.features.wallet.models.isSendableAmount
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
import kotlin.properties.ReadOnlyProperty
import org.rekotlin.StateType
data class WalletState(
val state: ProgressState = ProgressState.Done,
@ -62,7 +70,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 }
@ -319,6 +327,7 @@ sealed interface WalletDialog : StateDialog {
val currenciesList: List<FiatCurrency>,
val currentAppCurrency: FiatCurrency,
) : WalletDialog
object RussianCardholdersWarningDialog : WalletDialog
}
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
@ -363,18 +372,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) },
)
}
}
}
@ -404,19 +416,10 @@ data class WalletData(
}
fun assembleWarnings(): List<WalletWarning> {
val blockchain = currency.blockchain
val walletWarnings = mutableListOf<WalletWarning>()
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress)
}
if (currency.isBlockchain()) {
if (blockchain == Blockchain.Solana || blockchain == Blockchain.SolanaTestnet) {
val card = store.state.globalState.scanResponse?.card
if (card?.canHandleToken(blockchain) == false) {
walletWarnings.add(WalletWarning.SolanaTokensUnsupported)
}
}
}
if (walletRent != null) {
walletWarnings.add(WalletWarning.Rent(walletRent))
}

View file

@ -8,12 +8,13 @@ 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.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
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.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
@ -24,51 +25,70 @@ class TradeCryptoMiddleware {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
is WalletAction.TradeCryptoAction.Buy -> startExchange(action)
is WalletAction.TradeCryptoAction.Sell -> startExchange(action)
is WalletAction.TradeCryptoAction.Buy -> proceedBuyAction(state, action)
is WalletAction.TradeCryptoAction.Sell -> proceedSellAction()
is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
}
}
private fun startExchange(action: WalletAction.TradeCryptoAction) {
val selectedWalletData = store.state.walletState.getSelectedWalletData()
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
val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) {
CurrencyExchangeManager.Action.Buy
} else {
CurrencyExchangeManager.Action.Sell
private fun proceedBuyAction(
state: () -> AppState?,
action: WalletAction.TradeCryptoAction.Buy,
) {
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
store.dispatchOnMain(
WalletAction.DialogAction.RussianCardholdersWarningDialog
)
return
}
if (exchangeAction == CurrencyExchangeManager.Action.Buy &&
currency is Currency.Token && currency.blockchain.isTestnet()
) {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
val exchangeManager = store.state.globalState.exchangeManager ?: return
val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val currency = selectedWalletData.currency
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
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
}
exchangeManager.getUrl(
action = exchangeAction,
action = CurrencyExchangeManager.Action.Buy,
blockchain = currency.blockchain,
cryptoCurrencyName = currencySymbol,
cryptoCurrencyName = currency.currencySymbol,
fiatCurrencyName = appCurrency.code,
walletAddress = defaultAddress
walletAddress = addresses[0].address
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
}
private fun proceedSellAction() {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
val exchangeManager = store.state.globalState.exchangeManager ?: return
val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val currency = selectedWalletData.currency
exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Sell,
blockchain = currency.blockchain,
cryptoCurrencyName = currency.currencySymbol,
fiatCurrencyName = appCurrency.code,
walletAddress = addresses[0].address
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
}
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
@ -89,7 +109,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 {

View file

@ -27,10 +27,13 @@ class WalletDialogsMiddleware {
is WalletAction.DialogAction.ChooseCurrency -> {
store.dispatchDialogShow(
WalletDialog.SelectAmountToSendDialog(
amounts = action.amounts
amounts = action.amounts
)
)
}
is WalletAction.DialogAction.RussianCardholdersWarningDialog -> {
store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog)
}
is WalletAction.DialogAction.Hide -> {
store.dispatchDialogHide()
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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
@ -107,7 +91,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
private fun setupButtons() = with(binding) {
rowButtons.onBuyClick = {
store.dispatch(WalletAction.TradeCryptoAction.Buy)
store.dispatch(WalletAction.TradeCryptoAction.Buy())
}
rowButtons.onSellClick = {
store.dispatch(WalletAction.TradeCryptoAction.Sell)
@ -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,
)
}

View file

@ -21,9 +21,6 @@ class WalletWarningConverter(
message.blockchainFullName, message.blockchainFullName
)
}
WalletWarning.SolanaTokensUnsupported -> {
context.getString(R.string.warning_token_send_unsupported_message)
}
WalletWarning.TransactionInProgress -> {
context.getString(R.string.wallet_pending_transaction_warning)
}

View file

@ -27,12 +27,12 @@ class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(c
}
binding!!.dialogBtnBuy.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy)
dismiss()
store.dispatch(WalletAction.TradeCryptoAction.Buy())
}
binding!!.dialogBtnSell.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Sell)
dismiss()
store.dispatch(WalletAction.TradeCryptoAction.Sell)
}
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
class RussianCardholdersWarningBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
private var binding: DialogRussiansCardholdersWarningBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogRussiansCardholdersWarningBinding
.inflate(LayoutInflater.from(context))
.also { setContentView(it.root) }
}
override fun show() {
super.show()
setOnDismissListener {
binding = null
store.dispatchDialogHide()
}
binding?.btnYes?.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false))
dismiss()
}
binding?.btnNo?.setOnClickListener {
store.dispatch(NavigationAction.OpenUrl(INSTRUCTION_URL))
dismiss()
}
}
companion object {
private const val INSTRUCTION_URL = "https://tangem.com/howtobuy.html"
}
}

View file

@ -1,12 +1,9 @@
package com.tangem.tap.features.wallet.ui.images
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import coil.imageLoader
import coil.load
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
import com.tangem.blockchain.common.Blockchain
@ -29,67 +26,117 @@ fun loadCurrencyIcon(
token: Token?,
blockchain: Blockchain,
) {
when {
token == null -> currencyImageView.loadIcon(
iconUrl = getIconUrl(blockchain.toNetworkId()),
placeholderRes = blockchain.getRoundIconRes(),
CurrencyIconLoader(
currencyImageView = currencyImageView,
currencyTextView = currencyTextView,
token = token,
blockchain = blockchain
)
.load()
}
private class CurrencyIconLoader(
private val currencyImageView: ImageFilterView,
private val currencyTextView: TextView,
private val token: Token?,
private val blockchain: Blockchain,
) {
fun load() {
when {
token == null && blockchain.isTestnet() -> loadTestnetBlockchainIcon()
token == null -> loadBlockchainIcon()
blockchain.isTestnet() -> loadTestnetTokenIcon()
else -> loadTokenIcon()
}
}
private fun loadBlockchainIcon() {
loadBlockchainIconBase(
onStart = {
if (blockchain.isTestnet()) {
currencyImageView.saturation = 0f
} else {
currencyImageView.colorFilter = null
}
currencyImageView.colorFilter = null
}
)
token.symbol == QCX -> currencyImageView.load(R.drawable.ic_qcx)
token.symbol == VOYR -> currencyImageView.load(R.drawable.ic_voyr)
else -> currencyImageView.loadIcon(
iconUrl = getTokenIconUrl(token, blockchain),
}
private fun loadTestnetBlockchainIcon() {
loadBlockchainIconBase(
onStart = {
currencyImageView.saturation = 0f
}
)
}
private fun loadTokenIcon() {
loadTokenIconBase(
onStart = {
currencyImageView.setColorFilter(it.getColor())
},
onSuccess = {
currencyImageView.colorFilter = null
}
)
}
private fun loadTestnetTokenIcon() {
loadTokenIconBase(
onStart = {
currencyImageView.saturation = 0f
}
)
}
private inline fun loadBlockchainIconBase(
crossinline onStart: (Blockchain) -> Unit = {},
crossinline onSuccess: (Blockchain) -> Unit = {},
crossinline onError: (Blockchain) -> Unit = {},
) {
currencyImageView.loadIcon(
data = getIconUrl(blockchain.toNetworkId()),
placeholderRes = blockchain.getRoundIconRes(),
onStart = { onStart(blockchain) },
onSuccess = { onSuccess(blockchain) },
onError = { onError(blockchain) },
)
}
private inline fun loadTokenIconBase(
crossinline onStart: (Token) -> Unit = {},
crossinline onSuccess: (Token) -> Unit = {},
crossinline onError: (Token) -> Unit = {},
) {
if (token == null) return
currencyImageView.loadIcon(
data = getTokenIcon(token, blockchain),
placeholderRes = R.drawable.shape_circle,
onStart = {
currencyTextView.text = token.symbol.take(1)
currencyTextView.setTextColor(token.getTextColor())
if (blockchain.isTestnet()) {
currencyImageView.saturation = 0f
}
},
onError = {
currencyImageView.colorFilter = PorterDuffColorFilter(
/* color = */
token.getColor(),
/* mode = */
PorterDuff.Mode.SRC_ATOP,
)
onStart(token)
},
onSuccess = {
if (!blockchain.isTestnet()) {
currencyImageView.colorFilter = null
}
}
currencyTextView.text = null
onSuccess(token)
},
onError = { onError(token) },
)
}
}
private inline fun ImageView.loadIcon(
iconUrl: String?,
data: Any?,
placeholderRes: Int,
crossinline onStart: () -> Unit = {},
crossinline onSuccess: () -> Unit = {},
crossinline onError: () -> Unit = {},
) {
ImageRequest.Builder(context)
.data(iconUrl)
.data(data)
.placeholder(placeholderRes)
.error(placeholderRes)
.fallback(placeholderRes)
.transformations(
RoundedCornersTransformation(
topLeft = 32f,
topRight = 32f,
bottomLeft = 32f,
bottomRight = 32f
)
RoundedCornersTransformation(radius = 8f)
)
.listener(
onStart = { onStart() },
@ -101,9 +148,15 @@ private inline fun ImageView.loadIcon(
.also(context.imageLoader::enqueue)
}
private fun getTokenIconUrl(token: Token, blockchain: Blockchain): String? {
return token.id?.let(::getIconUrl)
?: token.getCustomIconUrl()
?: IconsUtil.getTokenIconUri(blockchain, token)
?.toString()
private fun getTokenIcon(token: Token, blockchain: Blockchain): Any? {
return when (token.symbol) {
QCX -> R.drawable.ic_qcx
VOYR -> R.drawable.ic_voyr
else -> {
token.id?.let(::getIconUrl)
?: token.getCustomIconUrl()
?: IconsUtil.getTokenIconUri(blockchain, token)
?.toString()
}
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.tap.features.wallet.ui.images
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Shader
import androidx.annotation.Px
import androidx.core.graphics.applyCanvas
import coil.size.Size
import coil.size.pxOrElse
import coil.transform.Transformation
class RoundedCornersCropTransformation(
@Px val radiusPx: Float,
) : Transformation {
override val cacheKey: String = "${javaClass.name}-$radiusPx"
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
val outputWidth = size.width.pxOrElse { Int.MAX_VALUE }
val outputHeight = size.height.pxOrElse { Int.MAX_VALUE }
val outputSize = minOf(outputWidth, outputHeight)
val output = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888)
val rect = RectF(
/* left = */ 0f,
/* top = */ 0f,
/* right = */ outputWidth.toFloat(),
/* bottom = */ outputHeight.toFloat()
)
val matrix = Matrix().apply {
setTranslate(
/* dx = */ (outputWidth - input.width) * .5f,
/* dy = */ (outputHeight - input.height) * .5f
)
}
val bitmapShader = BitmapShader(
/* bitmap = */ input,
/* tileX = */ Shader.TileMode.DECAL,
/* tileY = */ Shader.TileMode.DECAL
).apply {
setLocalMatrix(matrix)
}
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply {
shader = bitmapShader
}
return output.applyCanvas {
drawRoundRect(
/* rect = */ rect,
/* rx = */ radiusPx,
/* ry = */ radiusPx,
/* paint = */ paint
)
}
}
}

View file

@ -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,10 +146,10 @@ 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.Buy()
!allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
allowedToBuy && allowedToSell -> WalletAction.DialogAction.ChooseTradeActionDialog
else -> null
@ -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 {

View file

@ -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)

View file

@ -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"
}
}

View file

@ -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,
)
}

View file

@ -0,0 +1,135 @@
package com.tangem.tap.network.exchangeServices.mercuryo
import android.net.Uri
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.calculateSha512
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
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,
private val secret: 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)
.appendQueryParameter("signature", signature(walletAddress))
.appendQueryParameter("fix_currency", "true")
.appendQueryParameter("return_url", ExchangeUrlBuilder.SUCCESS_URL)
val url = builder.build().toString()
return url
}
private fun signature(address: String): String {
return (address + secret).calculateSha512().toHexString().lowercase()
}
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() }
}
}

View file

@ -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>
)

View file

@ -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
)

View file

@ -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
)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:fillColor="#000"
android:pathData="M16.3,16.3C16.7,15.9 16.7,15.3 16.3,14.9L12.4,11L16.3,7.1C16.7,6.7 16.7,6.1 16.3,5.7C15.9,5.3 15.3,5.3 14.9,5.7L11,9.6L7.1,5.7C6.7,5.3 6.1,5.3 5.7,5.7C5.3,6.1 5.3,6.7 5.7,7.1L9.6,11L5.7,14.9C5.3,15.3 5.3,15.9 5.7,16.3C6.1,16.7 6.7,16.7 7.1,16.3L11,12.4L14.9,16.3C15.3,16.7 15.9,16.7 16.3,16.3Z" />
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="60"
android:viewportHeight="60">
<group>
<clip-path android:pathData="M0,0h60v60h-60z" />
<path
android:fillColor="#F0F0F0"
android:pathData="M30,60C46.569,60 60,46.569 60,30C60,13.432 46.569,0 30,0C13.432,0 0,13.432 0,30C0,46.569 13.432,60 30,60Z" />
<path
android:fillColor="#0052B4"
android:pathData="M58.134,40.435C59.34,37.185 60,33.67 60,30C60,26.33 59.34,22.815 58.134,19.565H1.866C0.66,22.815 0,26.33 0,30C0,33.67 0.66,37.185 1.866,40.435L30,43.044L58.134,40.435Z" />
<path
android:fillColor="#D80027"
android:pathData="M30,60C42.899,60 53.895,51.859 58.134,40.435H1.866C6.105,51.859 17.101,60 30,60Z" />
</group>
</vector>

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundWhite"
android:minHeight="420dp"
tools:layout_gravity="bottom">
<ImageView
android:id="@+id/iv_flag"
android:layout_width="68dp"
android:layout_height="68dp"
android:contentDescription="@null"
android:src="@drawable/img_flag_ru_24"
app:layout_constraintBottom_toTopOf="@id/tv_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<View
android:id="@+id/view_cross_outline"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/shape_circle"
android:backgroundTint="@color/backgroundWhite"
app:layout_constraintBottom_toBottomOf="@id/iv_cross"
app:layout_constraintEnd_toEndOf="@id/iv_cross"
app:layout_constraintStart_toStartOf="@id/iv_cross"
app:layout_constraintTop_toTopOf="@id/iv_cross" />
<ImageView
android:id="@+id/iv_cross"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="-4dp"
android:layout_marginBottom="-4dp"
android:background="@drawable/shape_circle"
android:backgroundTint="#FFEBEE"
android:contentDescription="@null"
android:scaleType="center"
android:src="@drawable/ic_cross_rounded_24"
app:layout_constraintBottom_toBottomOf="@id/iv_flag"
app:layout_constraintEnd_toEndOf="@id/iv_flag"
app:tint="#D80027" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="38dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="38dp"
android:text="@string/russian_bank_card_warning_title"
android:textAlignment="center"
android:textColor="@color/textBlack"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@id/tv_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_flag" />
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="38dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="38dp"
android:layout_marginBottom="38dp"
android:text="@string/russian_bank_card_warning_subtitle"
android:textAlignment="center"
android:textColor="@color/textBlack"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/btn_yes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_yes"
style="@style/OnboardingButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="38dp"
android:backgroundTint="@color/tapButtonColorBlack"
android:text="@string/common_yes"
android:textColor="@color/white"
app:cornerRadius="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_no"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_no"
style="@style/OnboardingButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="16dp"
android:backgroundTint="@color/buttonGray"
android:text="@string/common_no"
android:textColor="@color/textBlack"
app:cornerRadius="14dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_yes"
app:layout_constraintTop_toTopOf="@id/btn_yes" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="common_no">Nein</string>
<string name="common_save_changes">Änderungen speichern</string>
<string name="common_warning">Warnung</string>
<string name="common_retry">Erneut versuchen</string>
@ -196,4 +195,4 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS
<string name="xtz_withdrawal_message_reduce">Um %s XTZ reduzieren</string>
<string name="xtz_withdrawal_message_ignore">Nein, alles senden</string>
</resources>
</resources>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="common_no">Non </string>
<string name="common_save_changes">Sauvegarder les modifications </string>
<string name="common_warning"> Alerte </string>
<string name="common_retry"> Réessayer </string>
@ -188,4 +187,4 @@ Avertissement
<string name="xtz_withdrawal_message_reduce">Réduire de% s XTZ</string>
<string name="xtz_withdrawal_message_ignore">Non, envoyer toute la somme</string>
</resources>
</resources>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="common_no">No</string>
<string name="common_save_changes">Mantieni le modifiche</string>
<string name="common_warning">Avviso</string>
<string name="common_retry">Riprova</string>
@ -195,4 +194,4 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
<string name="xtz_withdrawal_message_reduce">Riduci di %s XTZ</string>
<string name="xtz_withdrawal_message_ignore">No, invia l\'intero importo</string>
</resources>
</resources>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="app_name">Tangem</string>
<string name="common_no">Нет</string>
<string name="common_save_changes">Сохранить изменения</string>
<string name="common_warning">Предупреждение</string>
<string name="common_retry">Повторить попытку</string>
@ -262,8 +261,8 @@
<string name="onboarding_done_body">Ваша криптокарта активирована и готова к использованию</string>
<string name="onboarding_done_button_continue">Продолжить</string>
<string name="onboarding_balance_title">Баланс</string>
<string name="address_qr_code_message_format">Отправляйте только %s (%s) на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере.</string>
<string name="address_qr_code_message_token_format">Отправляйте только %s (%s) из сети %s на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере.</string>
<string name="address_qr_code_message_format">Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств.</string>
<string name="address_qr_code_message_token_format">Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств.</string>
<string name="onboarding_twins_interrupt_warning">Если процесс повторного создания кошелька каким-либо образом прервется, вам придется начать все сначала.</string>
<string name="onboarding_twin_exit_warning">Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас.</string>
<string name="onboarding_error_create_primary_wallet">Внутренняя ошибка: не удается создать менеджер кошельков</string>
@ -382,4 +381,5 @@
<string name="feedback_preface_support">Привет, команда поддержки,</string>
<string name="feedback_preface_tx_push_failed">Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной.</string>
<string name="feedback_data_collection_message">Информация ниже не является обязательной. Вы можете стереть её, если хотите.</string>
<string name="common_no">Нет</string>
</resources>

View file

@ -50,4 +50,21 @@
<string name="main_processing_full_amount">В сумме учтены не все монеты</string>
<string name="main_manage_tokens">Управление токенами</string>
<string name="token_item_no_rate">Нет цены</string>
<string name="token_details_hide_token">Скрыть токен</string>
<string name="token_details_hide_alert_title">Скрыть %s</string>
<string name="token_details_hide_alert_hide">Скрыть</string>
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно.</string>
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
<string name="token_details_unable_hide_alert_message">Токен %s является основной валютой в сети %s и не может быть скрыт, до тех пор пока у вас в списке есть другие токены этой сети.</string>
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
<string name="wallet_currency_subtitle">Сеть %s</string>
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
<string name="russian_bank_card_warning_title">Карты банков РФ в данный момент не принимаются</string>
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны или платежной системы UnionPay?</string>
<string name="common_yes">Да</string>
</resources>

View file

@ -2,7 +2,6 @@
<string name="app_name" translatable="false">Tangem</string>
<string name="common_no">No</string>
<string name="common_save_changes">Save changes</string>
<string name="common_warning">Warning</string>
<string name="common_retry">Retry</string>

View file

@ -50,4 +50,23 @@
<string name="main_processing_full_amount">The amount does not include some of your funds</string>
<string name="main_manage_tokens">Manage tokens</string>
<string name="token_item_no_rate">No rate</string>
<string name="token_details_hide_token">Hide token</string>
<string name="token_details_hide_alert_title">Hide %s</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
<string name="wallet_currency_subtitle">%s network</string>
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
<string name="wallet_hide_token" translatable="false">Remove token</string>
<string name="russian_bank_card_warning_title">Russian bank cards are not accepted at the moment</string>
<string name="russian_bank_card_warning_subtitle">Do you have a bank card of another country or a UnionPay card?</string>
<string name="common_yes">Yes</string>
<string name="common_no">No</string>
</resources>

View file

@ -206,8 +206,8 @@
<string name="onboarding_done_body" translatable="false">Your crypto card is activated and ready to be used</string>
<string name="onboarding_done_button_continue" translatable="false">Continue</string>
<string name="onboarding_balance_title" translatable="false">Balance</string>
<string name="address_qr_code_message_format" translatable="false">Send only %s (%s) to this address. Sending any other currency will result in its irreversible loss.</string>
<string name="address_qr_code_message_token_format" translatable="false">Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss.</string>
<string name="address_qr_code_message_format" translatable="false">Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds.</string>
<string name="address_qr_code_message_token_format" translatable="false">Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds.</string>
<string name="onboarding_twins_interrupt_warning" translatable="false">If the process of re-creating the wallet gets interrupted in any way, you\'ll have to start over.</string>
<string name="onboarding_twin_exit_warning" translatable="false">The twinning process is partly complete. You can\'t exit it now.</string>

View file

@ -59,7 +59,7 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
implementation 'com.tangem:blockchain:develop-93'
implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97'
// implementation 'com.tangem:blockchain:0.0.1'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'

View file

@ -2,7 +2,6 @@ package com.tangem.domain.features.addCustomToken.redux
import android.webkit.ValueCallback
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.domain.AddCustomTokenError
@ -571,17 +570,11 @@ private class AddCustomTokenReducer(
supportedTokenNetworkIds = supportedTokenNetworkIds
)
var derivationPathState = state.screenState.derivationPath
derivationPathState = when (card.derivationStyle) {
DerivationStyle.LEGACY -> derivationPathState.copy(isVisible = true)
null, DerivationStyle.NEW -> derivationPathState.copy(isVisible = false)
}
val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain))
state.copy(
cardDerivationStyle = card.derivationStyle,
form = form,
tangemTechServiceManager = tangemTechServiceManager,
screenState = state.screenState.copy(derivationPath = derivationPathState)
)
}
is OnDestroy -> {

View file

@ -50,7 +50,7 @@ data class AddCustomTokenState(
fun blockchainToName(blockchain: Blockchain, isDerivationPath: Boolean = false): String? {
return when {
isDerivationPath -> blockchain.derivationPath(cardDerivationStyle)?.rawPath
isDerivationPath -> blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath
else -> {
when (blockchain) {
Blockchain.Unknown -> null
@ -150,10 +150,21 @@ data class AddCustomTokenState(
mainNetwork: Blockchain,
derivationNetwork: Blockchain,
derivationStyle: DerivationStyle?
): com.tangem.common.hdWallet.DerivationPath? = when (derivationNetwork) {
Blockchain.Unknown -> mainNetwork
else -> derivationNetwork
}.derivationPath(derivationStyle)
): com.tangem.common.hdWallet.DerivationPath? {
// If we allow user to select derivations, we need to provide different derivations
// (Legacy style derivations).
// But the mainNetwork derivation depends on whether a user has a card
// with legacy derivations or new style derivations.
val derivationStyleToUse = if (derivationNetwork == Blockchain.Unknown) {
derivationStyle
} else {
DerivationStyle.LEGACY
}
return when (derivationNetwork) {
Blockchain.Unknown -> mainNetwork
else -> derivationNetwork
}.derivationPath(derivationStyleToUse)
}
internal fun createFormFields(card: Card, type: CustomTokenType): List<DataField<*>> {
return listOf(

View file

@ -43,4 +43,8 @@ data class CurrenciesResponse(val currencies: List<Currency>) {
val unit: String, // $, €, ₽
val type: String,
) : TangemTechResponse
}
}
data class GeoResponse(
val code: String
) : TangemTechResponse

View file

@ -27,4 +27,6 @@ interface TangemTechApi {
@GET("currencies")
suspend fun currencies(): CurrenciesResponse
@GET("geo")
suspend fun geo(): GeoResponse
}

View file

@ -47,6 +47,10 @@ class TangemTechService {
}
}
suspend fun userCountry(): Result<GeoResponse> = withContext(Dispatchers.IO) {
performRequest { api.geo() }
}
suspend fun currencies(): Result<CurrenciesResponse> = withContext(Dispatchers.IO) {
performRequest { api.currencies() }
}