Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-12 10:12:17 +03:00
commit da4a98c4f2
475 changed files with 9814 additions and 4216 deletions

View file

@ -22,6 +22,9 @@ android {
resources.excludes.add("META-INF/LICENSE.md")
resources.excludes.add("META-INF/NOTICE.md")
}
androidResources {
generateLocaleConfig = true
}
}
configurations.all {
@ -87,6 +90,7 @@ dependencies {
implementation(projects.libs.crypto)
implementation(projects.libs.auth)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.tangemSdkApi)
implementation(projects.data.appCurrency)
implementation(projects.data.appTheme)
@ -143,6 +147,10 @@ dependencies {
implementation(projects.features.walletSettings.impl)
implementation(projects.features.markets.api)
implementation(projects.features.markets.impl)
implementation(projects.features.onramp.api)
implementation(projects.features.onramp.impl)
implementation(projects.features.onboardingV2.api)
implementation(projects.features.onboardingV2.impl)
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)

View file

@ -1,16 +1,22 @@
package com.tangem.common
import android.Manifest
import android.content.Context
import android.util.Log
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.espresso.intent.Intents
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.kaspersky.components.composesupport.config.withComposeSupport
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.tap.MainActivity
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.hilt.android.testing.HiltAndroidRule
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
@ -24,6 +30,9 @@ abstract class BaseTestCase : TestCase(
@Inject
lateinit var tangemSdkManager: TangemSdkManager
@Inject
lateinit var appPreferencesStore: AppPreferencesStore
@get:Rule
open val composeTestRule = createAndroidComposeRule<MainActivity>()
@ -48,9 +57,22 @@ abstract class BaseTestCase : TestCase(
hiltRule.inject()
Intents.init()
additionalBeforeSection()
runBlocking {
delay(INIT_DELAY)
}
}.after {
runBlocking {
appPreferencesStore.editData { prefs -> prefs.clear() }
}
additionalAfterSection()
Intents.release()
}
companion object {
private const val INIT_DELAY = 1000L
}
}

View file

@ -143,9 +143,6 @@ class DetailsScreenTest : BaseTestCase() {
}
}
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
step("Assert Link more cards button does not exist") {
linkMoreCardsButton.assertIsNotDisplayed()
}
step("Assert Card Settings button is visible") {
cardSettingsButton.assertIsDisplayed()
}

View file

@ -1,6 +1,7 @@
package com.tangem.tests
import android.content.Intent.ACTION_VIEW
import androidx.test.espresso.intent.matcher.UriMatchers
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.DisclaimerTestScreen
@ -9,6 +10,7 @@ import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.kakao.intent.KIntent
import org.hamcrest.Matchers
import org.junit.Test
@HiltAndroidTest
@ -29,7 +31,7 @@ class StoriesTest : BaseTestCase() {
step("Assert: browser opened") {
val expectedIntent = KIntent {
hasAction(ACTION_VIEW)
hasData(NEW_BUY_WALLET_URL)
hasData { toString().startsWith(NEW_BUY_WALLET_URL) }
}
expectedIntent.intended()
device.uiDevice.pressBack()

@ -1 +1 @@
Subproject commit cf01c91626bf58bdf6895c59a706b83c37a9e345
Subproject commit adbdabe422b0513640d1750f276298683d342f61

View file

@ -748,6 +748,16 @@
"networkId": "core/test"
}
]
},
{
"id": "casper-network",
"name": "Casper",
"symbol": "CSPR",
"networks": [
{
"networkId": "casper-network/test"
}
]
}
]
}

View file

@ -8,9 +8,9 @@ import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
@ -19,21 +19,19 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import dagger.hilt.EntryPoint
@ -50,7 +48,7 @@ interface ApplicationEntryPoint {
fun getAppStateHolder(): AppStateHolder
fun getAssetLoader(): AssetLoader
fun getIssuersConfigStorage(): IssuersConfigStorage
fun getFeatureTogglesManager(): FeatureTogglesManager
@ -60,18 +58,12 @@ interface ApplicationEntryPoint {
fun getWalletConnect2Repository(): WalletConnect2Repository
fun getWalletConnectSessionsRepository(): WalletConnectSessionsRepository
fun getScanCardProcessor(): ScanCardProcessor
fun getAppCurrencyRepository(): AppCurrencyRepository
fun getWalletManagersFacade(): WalletManagersFacade
fun getNetworksRepository(): NetworksRepository
fun getCurrenciesRepository(): CurrenciesRepository
fun getAppThemeModeRepository(): AppThemeModeRepository
fun getBalanceHidingRepository(): BalanceHidingRepository
@ -102,8 +94,6 @@ interface ApplicationEntryPoint {
fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
fun getGetCardInfoUseCase(): GetCardInfoUseCase
fun getUrlOpener(): UrlOpener
@ -119,4 +109,8 @@ interface ApplicationEntryPoint {
fun getHomeFeatureToggles(): HomeFeatureToggles
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
fun getOnrampFeatureToggles(): OnrampFeatureToggles
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
}

View file

@ -56,10 +56,10 @@ import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRou
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.extensions.init
import com.tangem.tap.common.ActivityResultCallbackHolder
import com.tangem.tap.common.DialogManager
@ -69,7 +69,6 @@ import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
@ -113,9 +112,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lateinit var appStateHolder: AppStateHolder
/** Router for opening tester menu */
@Inject
lateinit var testerRouter: TesterRouter
@Inject
lateinit var cardSdkOwner: CardSdkOwner
@ -309,17 +305,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
store.dispatch(
DaggerGraphAction.SetActivityDependencies(
testerRouter = testerRouter,
scanCardUseCase = scanCardUseCase,
walletRouter = walletRouter,
walletConnectInteractor = walletConnectInteractor,
tokenDetailsRouter = tokenDetailsRouter,
cardSdkConfigRepository = cardSdkConfigRepository,
sendRouter = sendRouter,
qrScanningRouter = qrScanningRouter,
emailSender = emailSender,
stakingRouter = stakingRouter,
pushNotificationsRouter = pushNotificationsRouter,
),
)
}

View file

@ -20,10 +20,10 @@ import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
@ -33,18 +33,17 @@ import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.LogConfig
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
@ -53,10 +52,8 @@ import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.tasks.product.DerivationsFinder
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -85,8 +82,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val environmentConfigStorage: EnvironmentConfigStorage
get() = entryPoint.getEnvironmentConfigStorage()
private val assetLoader: AssetLoader
get() = entryPoint.getAssetLoader()
private val issuersConfigStorage: IssuersConfigStorage
get() = entryPoint.getIssuersConfigStorage()
private val featureTogglesManager: FeatureTogglesManager
get() = entryPoint.getFeatureTogglesManager()
@ -100,9 +97,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val walletConnect2Repository: WalletConnect2Repository
get() = entryPoint.getWalletConnect2Repository()
private val walletConnectSessionsRepository: WalletConnectSessionsRepository
get() = entryPoint.getWalletConnectSessionsRepository()
private val scanCardProcessor: ScanCardProcessor
get() = entryPoint.getScanCardProcessor()
@ -112,12 +106,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val walletManagersFacade: WalletManagersFacade
get() = entryPoint.getWalletManagersFacade()
private val networksRepository: NetworksRepository
get() = entryPoint.getNetworksRepository()
private val currenciesRepository: CurrenciesRepository
get() = entryPoint.getCurrenciesRepository()
private val appThemeModeRepository: AppThemeModeRepository
get() = entryPoint.getAppThemeModeRepository()
@ -163,9 +151,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase
get() = entryPoint.getSendFeedbackEmailUseCase()
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
get() = entryPoint.getSaveBlockchainErrorUseCase()
private val getCardInfoUseCase: GetCardInfoUseCase
get() = entryPoint.getGetCardInfoUseCase()
@ -189,6 +174,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val getUserCountryUseCase: GetUserCountryUseCase
get() = entryPoint.getGetUserCountryCodeUseCase()
private val onrampFeatureToggles: OnrampFeatureToggles
get() = entryPoint.getOnrampFeatureToggles()
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
get() = entryPoint.getOnboardingV2FeatureToggles()
// endregion
override fun onCreate() {
@ -211,10 +202,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
runBlocking {
featureTogglesManager.init()
val config = environmentConfigStorage.initialize()
store.dispatch(GlobalAction.SetConfigManager(environmentConfigStorage))
initWithConfigDependency(environmentConfig = config)
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
}
loadNativeLibraries()
@ -244,13 +232,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
networkConnectionManager = networkConnectionManager,
cardScanningFeatureToggles = cardScanningFeatureToggles,
walletConnectRepository = walletConnect2Repository,
walletConnectSessionsRepository = walletConnectSessionsRepository,
scanCardProcessor = scanCardProcessor,
appCurrencyRepository = appCurrencyRepository,
walletManagersFacade = walletManagersFacade,
appStateHolder = appStateHolder,
networksRepository = networksRepository,
currenciesRepository = currenciesRepository,
appThemeModeRepository = appThemeModeRepository,
balanceHidingRepository = balanceHidingRepository,
walletsRepository = walletsRepository,
@ -259,19 +244,20 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
generateWalletNameUseCase = generateWalletNameUseCase,
cardRepository = cardRepository,
tangemSdkLogger = tangemSdkLogger,
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
getCardInfoUseCase = getCardInfoUseCase,
assetLoader = assetLoader,
issuersConfigStorage = issuersConfigStorage,
urlOpener = urlOpener,
shareManager = shareManager,
appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory,
homeFeatureToggles = homeFeatureToggles,
getUserCountryUseCase = getUserCountryUseCase,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
),
),
)

View file

@ -1,6 +1,7 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
/**
[REDACTED_AUTHOR]
@ -17,7 +18,12 @@ sealed class Token(
params: Map<String, String> = mapOf(),
) : Token("Token / Receive", event, params) {
class ScreenOpened : Receive("Receive Screen Opened")
class ScreenOpened(
val token: String,
) : Receive(
event = "Receive Screen Opened",
params = mapOf(TOKEN_PARAM to token),
)
class ButtonCopyAddress : Receive("Button - Copy Address")
class ButtonShareAddress : Receive("Button - Share Address")
}

View file

@ -43,8 +43,8 @@ fun Store<*>.dispatchNotification(resId: Int) {
dispatchOnMain(GlobalAction.ShowNotification(resId))
}
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean = false) {
state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent)
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet) {
state.globalState.tapWalletManager.onWalletSelected(userWallet)
}
fun Store<*>.dispatchErrorNotification(error: TapError) {

View file

@ -1,8 +0,0 @@
package com.tangem.tap.common.feature
/**
[REDACTED_AUTHOR]
*/
interface Feature {
fun featureIsSwitchedOn(): Boolean
}

View file

@ -2,7 +2,6 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
@ -74,8 +73,6 @@ sealed class GlobalAction : Action {
data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction()
data class SetConfigManager(val environmentConfigStorage: EnvironmentConfigStorage) : GlobalAction()
object ExchangeManager : GlobalAction() {
object Init : GlobalAction() {
data class Success(

View file

@ -12,7 +12,6 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.network.exchangeServices.BuyExchangeService
import com.tangem.tap.network.exchangeServices.CardExchangeRules
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
@ -56,8 +55,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
restoreAppCurrency()
}
is GlobalAction.ExchangeManager.Init -> {
val appStateSafe = appState() ?: return
val config = appStateSafe.globalState.environmentConfigStorage?.getConfigSync() ?: return
val config = store.inject(DaggerGraphState::environmentConfigStorage).getConfigSync()
scope.launch {
val scanResponseProvider: () -> ScanResponse? = {
@ -149,15 +147,10 @@ private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): Excha
}
private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
return BuyExchangeService(
mercuryoService = makeMercuryoExchangeService(environmentConfig),
return MercuryoService(
environment = MercuryoEnvironment.prod(
widgetId = environmentConfig.mercuryoWidgetId,
secret = environmentConfig.mercuryoSecret,
),
)
}
private fun makeMercuryoExchangeService(environmentConfig: EnvironmentConfig): MercuryoService {
val mercuryoEnvironment = MercuryoEnvironment.prod(
environmentConfig.mercuryoWidgetId,
environmentConfig.mercuryoSecret,
)
return MercuryoService(mercuryoEnvironment)
}

View file

@ -47,9 +47,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
is GlobalAction.RestoreAppCurrency.Success -> {
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.SetConfigManager -> {
globalState.copy(environmentConfigStorage = action.environmentConfigStorage)
}
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanResponse?.card ?: return globalState
val wallet = card.wallets

View file

@ -1,6 +1,5 @@
package com.tangem.tap.common.redux.global
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse
@ -16,7 +15,6 @@ data class GlobalState(
val onboardingState: OnboardingState = OnboardingState(),
val cardVerifiedOnline: Boolean = false,
val tapWalletManager: TapWalletManager = TapWalletManager(),
val environmentConfigStorage: EnvironmentConfigStorage? = null,
val appCurrency: AppCurrency = AppCurrency.Default,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,

View file

@ -38,7 +38,7 @@ internal class AddressInfoBottomSheetDialog(
override fun show() {
super.show()
Analytics.send(Token.Receive.ScreenOpened())
Analytics.send(Token.Receive.ScreenOpened(stateDialog.currency.currencySymbol))
showData(data = stateDialog.addressData)
}

View file

@ -6,8 +6,8 @@ import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.network.exchangeServices.DefaultRampManager
import com.tangem.tap.proxy.AppStateHolder
import dagger.Module

View file

@ -3,9 +3,9 @@ package com.tangem.tap.di
import android.content.Context
import com.tangem.domain.card.BuildConfig
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View file

@ -2,7 +2,7 @@ package com.tangem.tap.di.data
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.card.DefaultDerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module

View file

@ -9,9 +9,9 @@ import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase
import com.tangem.tap.domain.card.DefaultResetCardUseCase
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View file

@ -12,7 +12,7 @@ import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
import dagger.Module
import dagger.Provides

View file

@ -1,10 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
import com.tangem.domain.staking.repositories.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -51,6 +48,32 @@ internal object StakingDomainModule {
)
}
@Provides
@Singleton
fun provideFetchActionsUseCase(
stakingRepository: StakingRepository,
stakingActionRepository: StakingActionRepository,
stakingErrorResolver: StakingErrorResolver,
): FetchActionsUseCase {
return FetchActionsUseCase(
stakingRepository = stakingRepository,
stakingActionRepository = stakingActionRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetActionsUseCase(
stakingActionRepository: StakingActionRepository,
stakingErrorResolver: StakingErrorResolver,
): GetActionsUseCase {
return GetActionsUseCase(
stakingActionRepository = stakingActionRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetStakingTokensUseCase(
@ -123,38 +146,12 @@ internal object StakingDomainModule {
)
}
@Provides
@Singleton
fun provideSavePendingTransactionUseCase(
stakingPendingTransactionRepository: StakingPendingTransactionRepository,
stakingErrorResolver: StakingErrorResolver,
): SavePendingTransactionUseCase {
return SavePendingTransactionUseCase(
stakingPendingTransactionRepository = stakingPendingTransactionRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideInvalidatePendingTransactionsUseCase(
stakingPendingTransactionRepository: StakingPendingTransactionRepository,
stakingErrorResolver: StakingErrorResolver,
): InvalidatePendingTransactionsUseCase {
return InvalidatePendingTransactionsUseCase(
stakingPendingTransactionRepository = stakingPendingTransactionRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideGetPendingTransactionsUseCase(
stakingPendingTransactionRepository: StakingPendingTransactionRepository,
stakingErrorResolver: StakingErrorResolver,
): GetStakingPendingTransactionsUseCase {
return GetStakingPendingTransactionsUseCase(
stakingPendingTransactionRepository = stakingPendingTransactionRepository,
stakingErrorResolver = stakingErrorResolver,
)
}

View file

@ -7,7 +7,6 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -68,17 +67,6 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetCardTokensListUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): GetNodlTokenListUseCase {
return GetNodlTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository)
}
@Provides
@Singleton
fun provideRemoveCurrencyUseCase(
@ -234,7 +222,6 @@ internal object TokensDomainModule {
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
stakingFeatureToggles: StakingFeatureToggles,
marketsFeatureToggles: MarketsFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyActionsUseCase {
return GetCryptoCurrencyActionsUseCase(
@ -246,7 +233,6 @@ internal object TokensDomainModule {
networksRepository = networksRepository,
stakingRepository = stakingRepository,
stakingFeatureToggles = stakingFeatureToggles,
marketsFeatureToggles = marketsFeatureToggles,
dispatchers = dispatchers,
)
}
@ -287,6 +273,16 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetMinimumTransactionAmountSyncUseCase(
currencyChecksRepository: CurrencyChecksRepository,
): GetMinimumTransactionAmountSyncUseCase {
return GetMinimumTransactionAmountSyncUseCase(
currencyChecksRepository = currencyChecksRepository,
)
}
@Provides
@Singleton
fun provideIsCryptoCurrencyCoinCouldHideUseCase(

View file

@ -3,7 +3,6 @@ package com.tangem.tap.domain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.operations.attestation.Attestation
@ -28,19 +27,16 @@ class TapWalletManager(
field = value
}
suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
suspend fun onWalletSelected(userWallet: UserWallet) {
// If a previous job was running, it gets cancelled before the new one starts,
// ensuring that only one job is active at any given time.
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
.launch { loadUserWalletData(userWallet, sendAnalyticsEvent) }
.launch { loadUserWalletData(userWallet) }
.also { it.join() }
}
private suspend fun loadUserWalletData(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
private suspend fun loadUserWalletData(userWallet: UserWallet) {
Analytics.setContext(userWallet.scanResponse)
if (sendAnalyticsEvent) {
Analytics.send(Basic.WalletOpened())
}
val scanResponse = userWallet.scanResponse
val card = scanResponse.card
val attestationFailed = card.attestation.status == Attestation.Status.Failed

View file

@ -6,7 +6,7 @@ import arrow.core.right
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
internal class DefaultDeleteSavedAccessCodesUseCase(
private val tangemSdkManager: TangemSdkManager,

View file

@ -20,7 +20,7 @@ import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext

View file

@ -14,7 +14,7 @@ import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams
import com.tangem.domain.card.models.ResetCardError
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
internal class DefaultResetCardUseCase(
private val tangemSdkManager: TangemSdkManager,

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.card.repository.ScanCardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
// TODO: Move to the :data:card module

View file

@ -32,8 +32,9 @@ import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.tasks.product.*
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask

View file

@ -20,9 +20,9 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
@Suppress("TooManyFunctions")
class MockTangemSdkManager(

View file

@ -6,7 +6,7 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
interface MockContent {

View file

@ -4,10 +4,10 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.domain.models.scan.ProductType
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.content.NoteMockContent
import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent
import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
object MockProvider {

View file

@ -8,8 +8,8 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import java.util.Date
object NoteMockContent : MockContent {

View file

@ -13,8 +13,8 @@ import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import java.util.Date
object Wallet2MockContent : MockContent {

View file

@ -13,8 +13,8 @@ import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import java.util.Date
object WalletMockContent : MockContent {

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.settings
import com.tangem.domain.settings.repositories.LegacySettingsRepository
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.sdk.api.TangemSdkManager
internal class DefaultLegacySettingsRepository(
private val tangemSdkManager: TangemSdkManager,

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSession
@ -19,33 +18,15 @@ import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.KeyWalletPublicKey
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingCommand
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.read.ReadWalletsListCommand
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.operations.wallet.CreateWalletResponse as SdkCreateWalletResponse
data class CreateProductWalletTaskResponse(
val card: CardDTO,
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
val primaryCard: PrimaryCard? = null,
) : CommandResponse {
constructor(
card: Card,
derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
primaryCard: PrimaryCard? = null,
) : this(
card = CardDTO(card),
derivedKeys = derivedKeys,
primaryCard = primaryCard,
)
}
private data class CreateWalletResponse(
val cardId: String,
val wallet: CardDTO.Wallet,

View file

@ -13,17 +13,19 @@ import com.tangem.tap.common.extensions.inject
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.flow.MutableStateFlow
class TwinCardsManager(card: CardDTO) {
private val issuersConfigStorage by lazy(mode = LazyThreadSafetyMode.NONE) {
store.inject(DaggerGraphState::issuersConfigStorage)
}
private val firstCardId: String = card.cardId
private val publicKey: String = card.issuer.publicKey.toHexString()
private var currentCardPublicKey: String? = null
private var secondCardPublicKey: String? = null
private val issuerKeyPairFlow = MutableStateFlow<KeyPair?>(value = null)
suspend fun createFirstWallet(message: Message): CompletionResult<CreateWalletResponse> {
val response = tangemSdkManager.createFirstTwinWallet(cardId = firstCardId, initialMessage = message)
@ -70,23 +72,11 @@ class TwinCardsManager(card: CardDTO) {
}
private suspend fun getIssuerKeys(): KeyPair {
issuerKeyPairFlow.value?.let { return it }
val assetLoader = store.inject(DaggerGraphState::assetLoader)
val issuer = assetLoader.loadList<Issuer>(fileName = ISSUERS_FILE_NAME)
.first { it.publicKey == publicKey }
val issuer = issuersConfigStorage.getConfig().first { it.publicKey == publicKey }
return KeyPair(
publicKey = issuer.publicKey.hexToBytes(),
privateKey = issuer.privateKey.hexToBytes(),
).also {
issuerKeyPairFlow.value = it
}
)
}
private companion object {
const val ISSUERS_FILE_NAME = "tangem-app-config/issuers"
}
}
private class Issuer(val privateKey: String, val publicKey: String)
}

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles
internal class TangemWcBlockchainHelper(
@ -23,6 +24,12 @@ internal class TangemWcBlockchainHelper(
return blockchain?.toNetworkId()
}
override fun chainIdsToBlockchains(chainIds: List<String>): List<Blockchain> {
return chainIds.mapNotNull {
it.parseId()?.chainIdToBlockchain()
}.distinct()
}
override fun chainIdToMissingNetworkNameOrNull(chainId: String): String? {
val parsedId = chainId.parseId() ?: return null
val blockchain = parsedId.chainIdToBlockchain()
@ -35,11 +42,12 @@ internal class TangemWcBlockchainHelper(
}
}
override fun networkIdToChainIdOrNull(networkId: String): String? {
override fun networkIdToChainIdOrNull(networkId: String): List<String> {
val blockchain = Blockchain.fromNetworkId(networkId)
val namespace = blockchain?.getCaip2Namespace() ?: return null
val chainId = blockchain.getCaip2ChainId() ?: return null
return "$namespace$CHAIN_SEPARATOR$chainId"
val namespace = blockchain?.getCaip2Namespace() ?: return emptyList()
return blockchain.getCaip2ChainIds().map {
"$namespace$CHAIN_SEPARATOR$it"
}
}
override fun getNamespaceFromFullChainIdOrNull(chainId: String): String? {
@ -52,8 +60,18 @@ internal class TangemWcBlockchainHelper(
return Blockchain.fromNetworkId(networkId)?.fullName
}
private fun Blockchain.getCaip2ChainId(): String? {
if (this.isEvm()) return this.getChainId()?.toString()
override fun chainIdsToAccounts(
walletAddress: String,
chainIds: List<String>,
derivationPath: String?,
): List<Account> {
return chainIds.map { chainId ->
Account(chainId, walletAddress, derivationPath)
}
}
private fun Blockchain.getCaip2ChainIds(): List<String> {
if (this.isEvm()) return listOfNotNull(this.getChainId()?.toString())
return when (this) {
/*
@ -61,13 +79,12 @@ internal class TangemWcBlockchainHelper(
* uncommented is used.
* Docs: https://docs.walletconnect.com/advanced/multichain/chain-list
*
* Blockchain.Solana -> "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
* */
Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ"
Blockchain.SolanaTestnet -> "z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z"
Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182"
Blockchain.Tron -> "0x2b6653dc"
else -> null
Blockchain.Solana -> listOf("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")
Blockchain.SolanaTestnet -> listOf("z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z")
Blockchain.Polkadot -> listOf("91b171bb158e2d3848fa23a9f1c25182")
Blockchain.Tron -> listOf("0x2b6653dc")
else -> emptyList()
}
}
@ -92,6 +109,7 @@ internal class TangemWcBlockchainHelper(
second.toIntOrNull()
?.let(Blockchain::fromChainId)
}
SOLANA_NAMESPACE -> Blockchain.Solana
else -> {
Blockchain.fromNetworkId(networkId = first)
.takeIf(supportedNonEvmBlockchains::contains)
@ -101,6 +119,7 @@ internal class TangemWcBlockchainHelper(
private companion object {
const val EVM_NAMESPACE = "eip155"
const val SOLANA_NAMESPACE = "solana"
const val CHAIN_SEPARATOR = ":"
const val TESTNET_SEPARATOR = "/"
}

View file

@ -19,7 +19,7 @@ internal class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler {
WalletConnectDialog.SessionProposalDialog(
sessionProposal = proposal,
networks = networksFormatted,
onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal) },
onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal(proposal)) },
onReject = { store.dispatchOnMain(WalletConnectAction.RejectProposal) },
),
),

View file

@ -207,6 +207,10 @@ internal class DefaultLegacyWalletConnectRepository(
Timber.i("onSessionDelete: $sessionDelete")
}
override fun onSessionExtend(session: Wallet.Model.Session) {
Timber.i("onSessionExtend: $session")
}
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
// Triggered when wallet receives the session settlement response from Dapp
Timber.i("onSessionSettleResponse: $settleSessionResponse")
@ -264,8 +268,6 @@ internal class DefaultLegacyWalletConnectRepository(
}
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>) {
this.userNamespaces = userNamespaces
val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal)
val userChains = userNamespaces.flatMap { namespace ->

View file

@ -102,7 +102,7 @@ class WalletConnectInteractor(
private suspend fun setupUserChains(userWallet: UserWallet, currencies: List<CryptoCurrency>) {
val accounts = getAccountsForWc(
userWallet = userWallet,
networks = currencies.map { it.network },
networks = currencies.map { it.network }.distinct(),
)
setUserChains(accounts)
handleDeeplinkStack(accounts)
@ -403,18 +403,14 @@ class WalletConnectInteractor(
derivationPath = it.derivationPath.value,
)
}
return walletManagers.mapNotNull {
return walletManagers.flatMap {
val wallet = it.wallet
val chainId = blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
val chainIds = blockchainHelper.networkIdToChainIdOrNull(wallet.blockchain.toNetworkId())
blockchainHelper.chainIdsToAccounts(
walletAddress = wallet.address,
chainIds = chainIds,
derivationPath = wallet.publicKey.derivationPath?.rawPath,
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
}

View file

@ -1,13 +1,20 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.domain.walletconnect2.domain.models.Account
interface WcBlockchainHelper {
fun chainIdToNetworkIdOrNull(chainId: String): String?
fun chainIdToMissingNetworkNameOrNull(chainId: String): String?
fun networkIdToChainIdOrNull(networkId: String): String?
fun networkIdToChainIdOrNull(networkId: String): List<String>
fun getNamespaceFromFullChainIdOrNull(chainId: String): String?
fun chainIdToFullNameOrNull(chainId: String): String?
fun chainIdsToAccounts(walletAddress: String, chainIds: List<String>, derivationPath: String?): List<Account>
fun chainIdsToBlockchains(chainIds: List<String>): List<Blockchain>
}

View file

@ -223,14 +223,14 @@ internal class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private
WcRequest.AddChain(data = deserializedParams)
}
WcJrpcMethods.SOLANA_SIGN_TX -> {
val tx = moshi.adapter<SolanaTransactionRequest>(SolanaTransactionRequest::class.java)
val tx = moshi.adapter(SolanaTransactionRequest::class.java)
.fromJsonOrNull(params)
?: return customRequest
WcRequest.SolanaSignRequest(data = tx)
}
WcJrpcMethods.SOLANA_SIGN_MESSAGE -> {
val signMessage = moshi.adapter<SolanaSignMessage>(SolanaSignMessage::class.java)
val signMessage = moshi.adapter(SolanaSignMessage::class.java)
.fromJsonOrNull(params)
?: return customRequest
val data = WcSignMessage(

View file

@ -1,12 +1,12 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
import okio.ByteString.Companion.decodeBase64
internal class WcSessionRequestConverter(
private val blockchainHelper: WcBlockchainHelper,
@ -115,11 +115,11 @@ internal class WcSessionRequestConverter(
)
}
is WcRequest.SolanaSignRequest -> {
val data = request.data.mapToTransaction()
val transaction = request.data.transaction
WcPreparedRequest.SignTransaction(
preparedRequestData = WcGenericTransactionData(
hashToSign = data.getSerializedMessage(),
hashToSign = transaction.prepareSolanaTransaction(),
dAppName = sessionRequest.metaName,
type = TransactionType.SOLANA_TX,
),
@ -132,6 +132,16 @@ internal class WcSessionRequestConverter(
}
}
/**
* Input transaction in Base64 string
*/
private fun String.prepareSolanaTransaction(): ByteArray {
return this.decodeBase64()
?.toByteArray()
?.drop(SOLANA_SIGNATURE_PLACEHOLDER_LENGTH)
?.toByteArray() ?: ByteArray(0)
}
private fun getWalletAddress(request: WcRequest): String? {
return when (request) {
is WcRequest.BnbTrade -> request.data.accountNumber
@ -156,4 +166,8 @@ internal class WcSessionRequestConverter(
it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase()
}?.derivationPath
}
companion object {
private const val SOLANA_SIGNATURE_PLACEHOLDER_LENGTH = 65
}
}

View file

@ -14,6 +14,9 @@ data class SolanaTransactionRequest(
@Json(name = "instructions")
val instructions: List<Instruction>,
@Json(name = "transaction")
val transaction: String,
) : WcRequestData {
@JsonClass(generateAdapter = true)
@ -22,7 +25,7 @@ data class SolanaTransactionRequest(
val programId: String,
@Json(name = "data")
val data: List<Byte>,
val data: String,
@Json(name = "keys")
val keys: List<Key>,

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
import org.rekotlin.Action
@ -22,7 +23,7 @@ sealed class WalletConnectAction : Action {
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
//region WalletConnect 2.0
data object ApproveProposal : WalletConnectAction()
data class ApproveProposal(val proposal: WalletConnectEvents.SessionProposal) : WalletConnectAction()
data object RejectProposal : WalletConnectAction()
data object SessionEstablished : WalletConnectAction()

View file

@ -13,7 +13,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -86,19 +85,21 @@ class WalletConnectMiddleware {
}
is WalletConnectAction.ApproveProposal -> {
scope.launch {
val proposalChainIds = action.proposal.requiredChainIds + action.proposal.optionalChainIds
val proposalBlockchains =
walletConnectInteractor.blockchainHelper.chainIdsToBlockchains(proposalChainIds)
val accounts = getWalletManagers()
.mapNotNull {
.filter { walletManager -> proposalBlockchains.contains(walletManager.wallet.blockchain) }
.flatMap {
val wallet = it.wallet
val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
val chainIds = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
walletConnectInteractor.blockchainHelper.chainIdsToAccounts(
walletAddress = wallet.address,
chainIds = chainIds,
derivationPath = wallet.publicKey.derivationPath?.rawPath,
)
}
walletConnectInteractor.approveSessionProposal(accounts)
}

View file

@ -18,13 +18,13 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.*
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode

View file

@ -6,10 +6,10 @@ import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled
import com.tangem.tap.store

View file

@ -7,10 +7,10 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions

View file

@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
import com.tangem.domain.balancehiding.BalanceHidingSettings
@ -103,7 +102,6 @@ internal class MainViewModel @Inject constructor(
.distinctUntilChanged()
.onEach { userWallet ->
Analytics.setContext(userWallet.scanResponse)
Analytics.send(Basic.WalletOpened())
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)

View file

@ -66,6 +66,10 @@ object OnboardingHelper {
}
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) {
return AppRoute.Onboarding(scanResponse)
}
return when (val type = scanResponse.productType) {
ProductType.Note -> AppRoute.OnboardingNote
ProductType.Wallet,

View file

@ -2,11 +2,11 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import android.net.Uri
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.common.card.Card
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import kotlinx.coroutines.CoroutineScope
import org.rekotlin.Action

View file

@ -28,13 +28,13 @@ import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSo
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingDialog

View file

@ -54,6 +54,15 @@ object TradeCryptoMiddleware {
}
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
if (isOnrampEnabled) proceedWithOnramp() else proceedWithLegacyBuyAction(state, action)
}
private fun proceedWithOnramp() {
store.dispatchNavigationAction { push(AppRoute.Onramp) }
}
private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
?.let(NetworkAddress.Address::value)

View file

@ -1,60 +0,0 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.scope
import kotlinx.coroutines.launch
/**
[REDACTED_AUTHOR]
* Temporary wrapper for the buy services. Service switches based on selected product type.
*/
internal class BuyExchangeService(
private val mercuryoService: MercuryoService,
) : ExchangeService, ExchangeUrlBuilder {
init {
scope.launch {
mercuryoService.update()
}
}
private val currentService: ExchangeService = mercuryoService
override suspend fun update() {
currentService.update()
}
override fun featureIsSwitchedOn(): Boolean = currentService.featureIsSwitchedOn()
override fun isBuyAllowed(): Boolean = currentService.isBuyAllowed()
override fun isSellAllowed(): Boolean = currentService.isSellAllowed()
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean =
currentService.availableForBuy(scanResponse, currency)
override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency)
override fun getUrl(
action: CurrencyExchangeManager.Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String? {
return currentService.getUrl(
action,
cryptoCurrency,
fiatCurrencyName,
walletAddress,
isDarkTheme,
)
}
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? {
return currentService.getSellCryptoReceiptUrl(action, transactionId)
}
}

View file

@ -13,12 +13,6 @@ class CardExchangeRules(
val cardProvider: () -> CardDTO?,
) : ExchangeRules {
override fun featureIsSwitchedOn(): Boolean {
val card = cardProvider() ?: return false
return !card.isStart2Coin
}
override fun isBuyAllowed(): Boolean {
val card = cardProvider() ?: return false

View file

@ -30,8 +30,6 @@ class CurrencyExchangeManager(
private val primaryRules: ExchangeRules,
) : ExchangeService {
override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn()
override suspend fun update() {
buyService.update()
sellService.update()

View file

@ -2,7 +2,6 @@ package com.tangem.tap.network.exchangeServices
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.common.feature.Feature
import com.tangem.tap.domain.model.Currency
interface Exchanger {
@ -12,12 +11,11 @@ interface Exchanger {
fun availableForSell(currency: Currency): Boolean
}
interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder {
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
suspend fun update()
companion object {
fun dummy(): ExchangeService = object : ExchangeService {
override fun featureIsSwitchedOn(): Boolean = false
override suspend fun update() {}
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
@ -39,11 +37,10 @@ interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder {
}
}
interface ExchangeRules : Feature, Exchanger {
interface ExchangeRules : Exchanger {
companion object {
fun dummy(): ExchangeRules = object : ExchangeRules {
override fun featureIsSwitchedOn(): Boolean = false
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false

View file

@ -0,0 +1,145 @@
package com.tangem.tap.network.exchangeServices.mercuryo
import com.tangem.blockchain.common.Blockchain
/**
* Map [Blockchain] to ids from [link](https://api.mercuryo.io/v1.6/lib/currencies) [crypto_currencies]
*/
@Suppress("CyclomaticComplexMethod")
internal val Blockchain.mercuryoNetwork: String?
get() {
return when (this) {
Blockchain.Algorand -> "ALGORAND"
Blockchain.Arbitrum -> "ARBITRUM"
Blockchain.Avalanche -> "AVALANCHE"
Blockchain.BSC -> "BINANCESMARTCHAIN"
Blockchain.Bitcoin -> "BITCOIN"
Blockchain.BitcoinCash -> "BITCOINCASH"
Blockchain.Cardano -> "CARDANO"
Blockchain.Cosmos -> "COSMOS"
Blockchain.Dash -> "DASH"
Blockchain.Dogecoin -> "DOGECOIN"
Blockchain.Ethereum -> "ETHEREUM"
Blockchain.Fantom -> "FANTOM"
Blockchain.Kusama -> "KUSAMA"
Blockchain.Litecoin -> "LITECOIN"
Blockchain.Near -> "NEAR_PROTOCOL"
Blockchain.TON -> "NEWTON"
Blockchain.Optimism -> "OPTIMISM"
Blockchain.Polkadot -> "POLKADOT"
Blockchain.Polygon -> "POLYGON"
Blockchain.XRP -> "RIPPLE"
Blockchain.Solana -> "SOLANA"
Blockchain.Stellar -> "STELLAR"
Blockchain.Tezos -> "TEZOS"
Blockchain.Tron -> "TRON"
Blockchain.ZkSyncEra -> "ZKSYNC"
Blockchain.Base -> "BASE"
Blockchain.ArbitrumTestnet -> null
Blockchain.AvalancheTestnet -> null
Blockchain.Binance -> null
Blockchain.BinanceTestnet -> null
Blockchain.BSCTestnet -> null
Blockchain.BitcoinTestnet -> null
Blockchain.BitcoinCashTestnet -> null
Blockchain.CosmosTestnet -> null
Blockchain.Ducatus -> null
Blockchain.EthereumTestnet -> null
Blockchain.EthereumClassic -> null
Blockchain.EthereumClassicTestnet -> null
Blockchain.FantomTestnet -> null
Blockchain.NearTestnet -> null
Blockchain.PolkadotTestnet -> null
Blockchain.Kava -> null
Blockchain.KavaTestnet -> null
Blockchain.PolygonTestnet -> null
Blockchain.RSK -> null
Blockchain.Sei -> null
Blockchain.SeiTestnet -> null
Blockchain.StellarTestnet -> null
Blockchain.SolanaTestnet -> null
Blockchain.TronTestnet -> null
Blockchain.Gnosis -> null
Blockchain.OptimismTestnet -> null
Blockchain.Dischain -> null
Blockchain.EthereumPow -> null
Blockchain.EthereumPowTestnet -> null
Blockchain.Kaspa -> null
Blockchain.Telos -> null
Blockchain.TelosTestnet -> null
Blockchain.TONTestnet -> null
Blockchain.Ravencoin -> null
Blockchain.RavencoinTestnet -> null
Blockchain.TerraV1 -> null
Blockchain.TerraV2 -> null
Blockchain.Cronos -> null
Blockchain.AlephZero -> null
Blockchain.AlephZeroTestnet -> null
Blockchain.OctaSpace -> null
Blockchain.OctaSpaceTestnet -> null
Blockchain.Chia -> null
Blockchain.ChiaTestnet -> null
Blockchain.Decimal -> null
Blockchain.DecimalTestnet -> null
Blockchain.XDC -> null
Blockchain.XDCTestnet -> null
Blockchain.VeChain -> null
Blockchain.VeChainTestnet -> null
Blockchain.Aptos -> null
Blockchain.AptosTestnet -> null
Blockchain.Playa3ull -> null
Blockchain.Shibarium -> null
Blockchain.ShibariumTestnet -> null
Blockchain.AlgorandTestnet -> null
Blockchain.Hedera -> null
Blockchain.HederaTestnet -> null
Blockchain.Aurora -> null
Blockchain.AuroraTestnet -> null
Blockchain.Areon -> null
Blockchain.AreonTestnet -> null
Blockchain.PulseChain -> null
Blockchain.PulseChainTestnet -> null
Blockchain.ZkSyncEraTestnet -> null
Blockchain.Nexa -> null
Blockchain.NexaTestnet -> null
Blockchain.Moonbeam -> null
Blockchain.MoonbeamTestnet -> null
Blockchain.Manta -> null
Blockchain.MantaTestnet -> null
Blockchain.PolygonZkEVM -> null
Blockchain.PolygonZkEVMTestnet -> null
Blockchain.Radiant -> null
Blockchain.BaseTestnet -> null
Blockchain.Moonriver -> null
Blockchain.MoonriverTestnet -> null
Blockchain.Mantle -> null
Blockchain.MantleTestnet -> null
Blockchain.Flare -> null
Blockchain.FlareTestnet -> null
Blockchain.Taraxa -> null
Blockchain.TaraxaTestnet -> null
Blockchain.Koinos -> null
Blockchain.KoinosTestnet -> null
Blockchain.Joystream -> null
Blockchain.Bittensor -> null
Blockchain.Filecoin -> null
Blockchain.Blast -> null
Blockchain.BlastTestnet -> null
Blockchain.Cyber -> null
Blockchain.CyberTestnet -> null
Blockchain.InternetComputer -> null
Blockchain.Sui -> null
Blockchain.SuiTestnet -> null
Blockchain.EnergyWebChain -> null
Blockchain.EnergyWebChainTestnet -> null
Blockchain.EnergyWebX -> null
Blockchain.EnergyWebXTestnet -> null
Blockchain.Casper -> null
Blockchain.CasperTestnet -> null
Blockchain.Core -> null
Blockchain.CoreTestnet -> null
Blockchain.Unknown -> null
Blockchain.Xodex -> null
Blockchain.Canxium -> null
}
}

View file

@ -23,8 +23,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
private val availableMercuryoCurrencies = CopyOnWriteArrayList<MercuryoCurrenciesResponse.MercuryoCryptoCurrency>()
override fun featureIsSwitchedOn(): Boolean = true
override fun isBuyAllowed(): Boolean = true
override fun isSellAllowed(): Boolean = false
@ -32,7 +30,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
if (!isBuyAllowed()) return false
val mercuryoNetwork = currency.blockchain.mercuryoNetwork()
val mercuryoNetwork = currency.blockchain.mercuryoNetwork
val contractAddress = (currency as? Currency.Token)?.token?.contractAddress ?: ""
val availableCurrency = availableMercuryoCurrencies.firstOrNull {
it.currencySymbol == currency.currencySymbol &&
@ -79,7 +77,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
.appendQueryParameter("redirect_url", ExchangeUrlBuilder.SUCCESS_URL)
if (isDarkTheme) builder.appendQueryParameter("theme", "1inch")
blockchain.mercuryoNetwork()?.let {
blockchain.mercuryoNetwork?.let {
builder.appendQueryParameter("network", it)
}
@ -93,37 +91,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
}
@Suppress("CyclomaticComplexMethod")
private fun Blockchain.mercuryoNetwork(): String? {
return when (this) {
// Blockchain.Algorand -> "ALGORAND" //TODO: Uncomment with algo support
Blockchain.Arbitrum -> "ARBITRUM"
Blockchain.Avalanche -> "AVALANCHE"
Blockchain.BSC -> "BINANCESMARTCHAIN"
Blockchain.Bitcoin -> "BITCOIN"
Blockchain.BitcoinCash -> "BITCOINCASH"
Blockchain.Cardano -> "CARDANO"
Blockchain.Cosmos -> "COSMOS"
Blockchain.Dash -> "DASH"
Blockchain.Dogecoin -> "DOGECOIN"
Blockchain.Ethereum -> "ETHEREUM"
Blockchain.Fantom -> "FANTOM"
Blockchain.Kusama -> "KUSAMA"
Blockchain.Litecoin -> "LITECOIN"
Blockchain.Near -> "NEAR_PROTOCOL"
Blockchain.TON -> "NEWTON"
Blockchain.Optimism -> "OPTIMISM"
Blockchain.Polkadot -> "POLKADOT"
Blockchain.Polygon -> "POLYGON"
Blockchain.XRP -> "RIPPLE"
Blockchain.Solana -> "SOLANA"
Blockchain.Stellar -> "STELLAR"
Blockchain.Tezos -> "TEZOS"
Blockchain.Tron -> "TRON"
else -> null
}
}
private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
private companion object {

View file

@ -1,37 +0,0 @@
package com.tangem.tap.network.exchangeServices.moonpay
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Blockchain.*
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPaySupportedCurrency
internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
get() = when (this) {
Algorand -> MoonPaySupportedCurrency(networkCode = "algorand", currencyCode = "algo")
Aptos -> MoonPaySupportedCurrency(networkCode = "aptos", currencyCode = "apt")
Arbitrum -> MoonPaySupportedCurrency(networkCode = "arbitrum", currencyCode = "eth_arbitrum")
Avalanche -> MoonPaySupportedCurrency(networkCode = "avalanche_c_chain", currencyCode = "avax_cchain")
Binance -> MoonPaySupportedCurrency(networkCode = "bnb_chain", currencyCode = "bnb")
Bitcoin -> MoonPaySupportedCurrency(networkCode = "bitcoin", currencyCode = "btc")
BitcoinCash -> MoonPaySupportedCurrency(networkCode = "bitcoin_cash", currencyCode = "bch")
BSC -> MoonPaySupportedCurrency(networkCode = "binance_smart_chain", currencyCode = "bnb_bsc")
Cardano -> MoonPaySupportedCurrency(networkCode = "cardano", currencyCode = "ada")
Cosmos -> MoonPaySupportedCurrency(networkCode = "cosmos", currencyCode = "atom")
Dogecoin -> MoonPaySupportedCurrency(networkCode = "dogecoin", currencyCode = "doge")
Ethereum -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "eth")
EthereumClassic -> MoonPaySupportedCurrency(networkCode = "ethereum_classic", currencyCode = "etc")
Hedera -> MoonPaySupportedCurrency(networkCode = "hedera", currencyCode = "hbar")
Litecoin -> MoonPaySupportedCurrency(networkCode = "litecoin", currencyCode = "ltc")
Near -> MoonPaySupportedCurrency(networkCode = "near", currencyCode = "near")
Optimism -> MoonPaySupportedCurrency(networkCode = "optimism", currencyCode = "eth_optimism")
Polkadot -> MoonPaySupportedCurrency(networkCode = "polkadot", currencyCode = "dot")
Polygon -> MoonPaySupportedCurrency(networkCode = "polygon", currencyCode = "matic_polygon")
Ravencoin -> MoonPaySupportedCurrency(networkCode = "ravencoin", currencyCode = "rvn")
Solana -> MoonPaySupportedCurrency(networkCode = "solana", currencyCode = "sol")
Stellar -> MoonPaySupportedCurrency(networkCode = "stellar", currencyCode = "xlm")
Tezos -> MoonPaySupportedCurrency(networkCode = "tezos", currencyCode = "xtz")
TON -> MoonPaySupportedCurrency(networkCode = "ton", currencyCode = "ton")
Tron -> MoonPaySupportedCurrency(networkCode = "tron", currencyCode = "trx")
VeChain -> MoonPaySupportedCurrency(networkCode = "vechain", currencyCode = "vet")
XRP -> MoonPaySupportedCurrency(networkCode = "ripple", currencyCode = "xrp")
else -> null
}

View file

@ -32,8 +32,6 @@ class MoonPayService(
private var status: MoonPayStatus? = null
override fun featureIsSwitchedOn(): Boolean = true
override suspend fun update() {
withIOContext {
performRequest {

View file

@ -0,0 +1,145 @@
package com.tangem.tap.network.exchangeServices.moonpay
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Blockchain.*
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPaySupportedCurrency
/**
* Map [Blockchain] to [MoonPaySupportedCurrency.networkCode] and [MoonPaySupportedCurrency.currencyCode]
* from [link](https://api.moonpay.com/v3/currencies/)
*/
internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
get() = when (this) {
Algorand -> MoonPaySupportedCurrency(networkCode = "algorand", currencyCode = "algo")
Aptos -> MoonPaySupportedCurrency(networkCode = "aptos", currencyCode = "apt")
Arbitrum -> MoonPaySupportedCurrency(networkCode = "arbitrum", currencyCode = "eth_arbitrum")
Avalanche -> MoonPaySupportedCurrency(networkCode = "avalanche_c_chain", currencyCode = "avax_cchain")
Binance -> MoonPaySupportedCurrency(networkCode = "bnb_chain", currencyCode = "bnb")
Bitcoin -> MoonPaySupportedCurrency(networkCode = "bitcoin", currencyCode = "btc")
BitcoinCash -> MoonPaySupportedCurrency(networkCode = "bitcoin_cash", currencyCode = "bch")
BSC -> MoonPaySupportedCurrency(networkCode = "binance_smart_chain", currencyCode = "bnb_bsc")
Cardano -> MoonPaySupportedCurrency(networkCode = "cardano", currencyCode = "ada")
Cosmos -> MoonPaySupportedCurrency(networkCode = "cosmos", currencyCode = "atom")
Dogecoin -> MoonPaySupportedCurrency(networkCode = "dogecoin", currencyCode = "doge")
Ethereum -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "eth")
EthereumClassic -> MoonPaySupportedCurrency(networkCode = "ethereum_classic", currencyCode = "etc")
Hedera -> MoonPaySupportedCurrency(networkCode = "hedera", currencyCode = "hbar")
Litecoin -> MoonPaySupportedCurrency(networkCode = "litecoin", currencyCode = "ltc")
Near -> MoonPaySupportedCurrency(networkCode = "near", currencyCode = "near")
Optimism -> MoonPaySupportedCurrency(networkCode = "optimism", currencyCode = "eth_optimism")
Polkadot -> MoonPaySupportedCurrency(networkCode = "polkadot", currencyCode = "dot")
Polygon -> MoonPaySupportedCurrency(networkCode = "polygon", currencyCode = "matic_polygon")
Ravencoin -> MoonPaySupportedCurrency(networkCode = "ravencoin", currencyCode = "rvn")
Solana -> MoonPaySupportedCurrency(networkCode = "solana", currencyCode = "sol")
Stellar -> MoonPaySupportedCurrency(networkCode = "stellar", currencyCode = "xlm")
Tezos -> MoonPaySupportedCurrency(networkCode = "tezos", currencyCode = "xtz")
TON -> MoonPaySupportedCurrency(networkCode = "ton", currencyCode = "ton")
Tron -> MoonPaySupportedCurrency(networkCode = "tron", currencyCode = "trx")
VeChain -> MoonPaySupportedCurrency(networkCode = "vechain", currencyCode = "vet")
XRP -> MoonPaySupportedCurrency(networkCode = "ripple", currencyCode = "xrp")
Sei -> MoonPaySupportedCurrency(networkCode = "sei", currencyCode = "sei_sei")
ZkSyncEra -> MoonPaySupportedCurrency(networkCode = "zksync", currencyCode = "eth_zksync")
Base -> MoonPaySupportedCurrency(networkCode = "base", currencyCode = "eth_base")
Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil")
Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui")
Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core")
ArbitrumTestnet -> null
AvalancheTestnet -> null
BinanceTestnet -> null
BSCTestnet -> null
BitcoinTestnet -> null
BitcoinCashTestnet -> null
CosmosTestnet -> null
Ducatus -> null
EthereumTestnet -> null
EthereumClassicTestnet -> null
Fantom -> null
FantomTestnet -> null
NearTestnet -> null
PolkadotTestnet -> null
Kava -> null // doesn't support KavaEvm
KavaTestnet -> null
Kusama -> null
PolygonTestnet -> null
RSK -> null
SeiTestnet -> null
StellarTestnet -> null
SolanaTestnet -> null
TronTestnet -> null
Gnosis -> null
Dash -> null
OptimismTestnet -> null
Dischain -> null
EthereumPow -> null
EthereumPowTestnet -> null
Kaspa -> null
Telos -> null
TelosTestnet -> null
TONTestnet -> null
RavencoinTestnet -> null
TerraV1 -> null
TerraV2 -> null
Cronos -> null
AlephZero -> null
AlephZeroTestnet -> null
OctaSpace -> null
OctaSpaceTestnet -> null
Chia -> null
ChiaTestnet -> null
Decimal -> null
DecimalTestnet -> null
XDC -> null
XDCTestnet -> null
VeChainTestnet -> null
AptosTestnet -> null
Playa3ull -> null
Shibarium -> null
ShibariumTestnet -> null
AlgorandTestnet -> null
HederaTestnet -> null
Aurora -> null
AuroraTestnet -> null
Areon -> null
AreonTestnet -> null
PulseChain -> null
PulseChainTestnet -> null
ZkSyncEraTestnet -> null
Nexa -> null
NexaTestnet -> null
Moonbeam -> null
MoonbeamTestnet -> null
Manta -> null
MantaTestnet -> null
PolygonZkEVM -> null
PolygonZkEVMTestnet -> null
Radiant -> null
BaseTestnet -> null
Moonriver -> null
MoonriverTestnet -> null
Mantle -> null
MantleTestnet -> null
Flare -> null
FlareTestnet -> null
Taraxa -> null
TaraxaTestnet -> null
Koinos -> null
KoinosTestnet -> null
Joystream -> null
Bittensor -> null
Blast -> null
BlastTestnet -> null
Cyber -> null
CyberTestnet -> null
InternetComputer -> null
SuiTestnet -> null
EnergyWebChain -> null
EnergyWebChainTestnet -> null
EnergyWebX -> null
EnergyWebXTestnet -> null
Casper -> null
CasperTestnet -> null
CoreTestnet -> null
Unknown -> null
Xodex -> null
Canxium -> null
}

View file

@ -5,11 +5,11 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import org.rekotlin.Action
import org.rekotlin.Store

View file

@ -1,31 +1,15 @@
package com.tangem.tap.proxy.redux
import com.tangem.core.navigation.email.EmailSender
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import org.rekotlin.Action
sealed interface DaggerGraphAction : Action {
data class SetActivityDependencies(
val testerRouter: TesterRouter,
val scanCardUseCase: ScanCardUseCase,
val walletRouter: WalletRouter,
val walletConnectInteractor: WalletConnectInteractor,
val tokenDetailsRouter: TokenDetailsRouter,
val cardSdkConfigRepository: CardSdkConfigRepository,
val sendRouter: SendRouter,
val qrScanningRouter: QrScanningRouter,
val emailSender: EmailSender,
val stakingRouter: StakingRouter,
val pushNotificationsRouter: PushNotificationsRouter,
) : DaggerGraphAction
}

View file

@ -13,17 +13,9 @@ object DaggerGraphReducer {
private fun internalReduce(action: DaggerGraphAction, state: AppState): DaggerGraphState {
return when (action) {
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
testerRouter = action.testerRouter,
scanCardUseCase = action.scanCardUseCase,
walletRouter = action.walletRouter,
walletConnectInteractor = action.walletConnectInteractor,
tokenDetailsRouter = action.tokenDetailsRouter,
cardSdkConfigRepository = action.cardSdkConfigRepository,
sendRouter = action.sendRouter,
qrScanningRouter = action.qrScanningRouter,
emailSender = action.emailSender,
stakingRouter = action.stakingRouter,
pushNotificationsRouter = action.pushNotificationsRouter,
)
}
}

View file

@ -1,14 +1,13 @@
package com.tangem.tap.proxy.redux
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
@ -17,43 +16,32 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.StateType
data class DaggerGraphState(
val testerRouter: TesterRouter? = null,
val networkConnectionManager: NetworkConnectionManager? = null,
val cardScanningFeatureToggles: CardScanningFeatureToggles? = null,
val scanCardUseCase: ScanCardUseCase? = null,
val walletRouter: WalletRouter? = null,
val walletConnectRepository: LegacyWalletConnectRepository? = null,
val walletConnectSessionsRepository: WalletConnectSessionsRepository? = null,
val walletConnectInteractor: WalletConnectInteractor? = null,
val tokenDetailsRouter: TokenDetailsRouter? = null,
val scanCardProcessor: ScanCardProcessor? = null,
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
val appCurrencyRepository: AppCurrencyRepository? = null,
@ -62,23 +50,16 @@ data class DaggerGraphState(
val appThemeModeRepository: AppThemeModeRepository? = null,
val balanceHidingRepository: BalanceHidingRepository? = null,
val walletsRepository: WalletsRepository? = null,
val networksRepository: NetworksRepository? = null,
val sendRouter: SendRouter? = null,
val qrScanningRouter: QrScanningRouter? = null,
val currenciesRepository: CurrenciesRepository? = null,
val generalUserWalletsListManager: UserWalletsListManager? = null,
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
val generateWalletNameUseCase: GenerateWalletNameUseCase? = null,
val cardRepository: CardRepository? = null,
val tangemSdkLogger: TangemSdkLogger? = null,
val settingsRepository: SettingsRepository? = null,
val blockchainSDKFactory: BlockchainSDKFactory? = null,
val emailSender: EmailSender? = null,
val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null,
val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null,
val getCardInfoUseCase: GetCardInfoUseCase? = null,
val assetLoader: AssetLoader? = null,
val issuersConfigStorage: IssuersConfigStorage? = null,
val stakingRouter: StakingRouter? = null,
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,
@ -87,4 +68,7 @@ data class DaggerGraphState(
val transactionSignerFactory: TransactionSignerFactory? = null,
val homeFeatureToggles: HomeFeatureToggles? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null,
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
) : StateType

View file

@ -11,6 +11,10 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.onramp.component.BuyCryptoComponent
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.OnrampComponent
import com.tangem.features.onramp.component.SellCryptoComponent
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
@ -46,6 +50,10 @@ internal class ChildFactory @Inject constructor(
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
private val onrampComponentFactory: OnrampComponent.Factory,
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
@ -186,6 +194,34 @@ internal class ChildFactory @Inject constructor(
componentFactory = marketsTokenDetailsComponentFactory,
)
}
is AppRoute.Onramp -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = OnrampComponent.Params(),
componentFactory = onrampComponentFactory,
)
}
is AppRoute.BuyCrypto -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = BuyCryptoComponent.Params(userWalletId = route.userWalletId),
componentFactory = buyCryptoComponentFactory,
)
}
is AppRoute.SellCrypto -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SellCryptoComponent.Params(userWalletId = route.userWalletId),
componentFactory = sellCryptoComponentFactory,
)
}
is AppRoute.Onboarding -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = OnboardingEntryComponent.Params(route.scanResponse),
componentFactory = onboardingEntryComponentFactory,
)
}
}
}

View file

@ -0,0 +1 @@
unqualifiedResLocale=en-US

View file

@ -1,3 +1,5 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
alias(deps.plugins.kotlin.android) apply false
alias(deps.plugins.kotlin.jvm) apply false
@ -20,6 +22,16 @@ interface Injected {
val fs: FileSystemOperations
}
// Test Logging
subprojects {
tasks.withType<Test> {
testLogging {
exceptionFormat = TestExceptionFormat.FULL
showStandardStreams = true
}
}
}
val assembleInternalQA by tasks.registering {
group = "build"
description = "Builds internal APK to 'build/outputs' directory"

View file

@ -7,6 +7,7 @@ import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.model.CryptoCurrency
@ -117,10 +118,6 @@ sealed class AppRoute(val path: String) : Route {
) : AppRoute(path = "/details/security"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
}
}
@Serializable
@ -288,4 +285,20 @@ sealed class AppRoute(val path: String) : Route {
val source: String,
)
}
@Serializable
data object Onramp : AppRoute(path = "/onramp")
@Serializable
data class BuyCrypto(
val userWalletId: UserWalletId,
) : AppRoute(path = "/buy_crypto/${userWalletId.stringValue}")
@Serializable
data class SellCrypto(
val userWalletId: UserWalletId,
) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}")
// Onboarding V2
data class Onboarding(val scanResponse: ScanResponse) : AppRoute(path = "/onboarding_v2")
}

View file

@ -4,10 +4,15 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.amountScreen.utils.checkExceedBalance
import com.tangem.common.ui.amountScreen.utils.getFiatValue
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
@ -22,8 +27,12 @@ import java.math.BigDecimal
*/
class AmountReduceByTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val minimumTransactionAmount: EnterAmountBoundary?,
private val value: ReduceByData,
) : Transformer<AmountState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
@ -40,24 +49,41 @@ class AmountReduceByTransformer(
decimals = fiatDecimals,
)
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false
val isZero = if (amountTextField.isFiatValue) {
decimalFiatValue.isNullOrZero()
} else {
decimalCryptoValue.isNullOrZero()
}
val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
return prevState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
isPrimaryButtonEnabled = !isZero && !isCheckFailed,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
isError = isExceedBalance,
error = resourceReference(R.string.send_validation_amount_exceeds_balance),
isError = isCheckFailed,
error = when {
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
isLessThanMinimumIfProvided -> {
val minimumAmount = minimumTransactionAmount
?.amount
?.format { crypto(cryptoCurrencyStatus.currency) }
.orEmpty()
resourceReference(
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
wrappedList(minimumAmount, minimumAmount),
)
}
else -> TextReference.EMPTY
},
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue),
imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),

View file

@ -4,11 +4,17 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.amountScreen.utils.checkExceedBalance
import com.tangem.common.ui.amountScreen.utils.getFiatValue
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
@ -22,8 +28,11 @@ import java.math.BigDecimal
*/
class AmountReduceToTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val minimumTransactionAmount: EnterAmountBoundary?,
private val value: BigDecimal,
) : Transformer<AmountState> {
private val maxEnterAmountConverter = MaxEnterAmountConverter()
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
@ -32,26 +41,44 @@ class AmountReduceToTransformer(
val fiatDecimals = amountTextField.fiatAmount.decimals
val cryptoValue = value.parseBigDecimal(cryptoDecimals)
val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals)
val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
isFiatValue = false,
decimals = fiatDecimals,
)
val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero()
val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
return prevState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
isPrimaryButtonEnabled = !isZero && !isCheckFailed,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
isError = isExceedBalance,
error = resourceReference(R.string.send_validation_amount_exceeds_balance),
isError = isCheckFailed,
error = when {
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
isLessThanMinimumIfProvided -> {
val minimumAmount = minimumTransactionAmount
?.amount
?.format { crypto(cryptoCurrencyStatus.currency) }
.orEmpty()
resourceReference(
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
wrappedList(minimumAmount, minimumAmount),
)
}
else -> TextReference.EMPTY
},
cryptoAmount = amountTextField.cryptoAmount.copy(value = value),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = getKeyboardAction(isExceedBalance, value),
imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),

View file

@ -3,17 +3,19 @@ package com.tangem.common.ui.amountScreen.converters
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.AmountParameters
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
@ -24,17 +26,17 @@ import kotlinx.collections.immutable.persistentListOf
*
* @property clickIntents amount screen clicks
* @property appCurrencyProvider selected app currency provider
* @property userWalletProvider selected user wallet provider
* @property maxEnterAmount max enter amount data
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
* @property iconStateConverter currency icon converter
*/
class AmountStateConverter(
private val clickIntents: AmountScreenClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val userWalletProvider: Provider<UserWallet>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val maxEnterAmount: EnterAmountBoundary,
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
) : Converter<String, AmountState> {
) : Converter<AmountParameters, AmountState> {
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
AmountFieldConverter(
@ -44,19 +46,18 @@ class AmountStateConverter(
)
}
override fun convert(value: String): AmountState {
val userWallet = userWalletProvider()
override fun convert(value: AmountParameters): AmountState {
val appCurrency = appCurrencyProvider()
val status = cryptoCurrencyStatusProvider()
val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol)
val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals)
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
val noFeeRate = status.value.fiatRate.isNullOrZero()
return AmountState.Data(
walletName = userWallet.name,
walletBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
title = value.title,
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
tokenIconState = iconStateConverter.convert(status),
amountTextField = amountFieldConverter.convert(value),
amountTextField = amountFieldConverter.convert(value.value),
isPrimaryButtonEnabled = false,
appCurrencyCode = appCurrency.code,
segmentedButtonConfig = persistentListOf(

View file

@ -0,0 +1,19 @@
package com.tangem.common.ui.amountScreen.converters
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.converter.Converter
/**
* Converts [CryptoCurrencyStatus] to [EnterAmountBoundary]
*/
class MaxEnterAmountConverter : Converter<CryptoCurrencyStatus, EnterAmountBoundary> {
override fun convert(value: CryptoCurrencyStatus): EnterAmountBoundary {
return EnterAmountBoundary(
amount = value.value.amount,
fiatAmount = value.value.fiatAmount,
fiatRate = value.value.fiatRate,
)
}
}

View file

@ -5,12 +5,16 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.amountScreen.utils.checkExceedBalance
import com.tangem.common.ui.amountScreen.utils.getCryptoValue
import com.tangem.common.ui.amountScreen.utils.getFiatValue
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
@ -20,11 +24,13 @@ import java.math.BigDecimal
/**
* Amount value change
*
* @property cryptoCurrencyStatus current cryptocurrency status
* @property maxEnterAmount max amount to enter
* @property value amount value
*/
class AmountFieldChangeTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val maxEnterAmount: EnterAmountBoundary,
private val minimumTransactionAmount: EnterAmountBoundary?,
private val value: String,
) : Transformer<AmountState> {
@ -39,36 +45,50 @@ class AmountFieldChangeTransformer(
val trimmedValue = value.trim()
val cryptoValue = trimmedValue.getCryptoValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
fiatRate = maxEnterAmount.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = cryptoDecimals,
)
val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals)
val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue(
fiatRate = cryptoCurrencyStatus.value.fiatRate,
fiatRate = maxEnterAmount.fiatRate,
isFiatValue = amountTextField.isFiatValue,
decimals = fiatDecimals,
)
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField)
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false
val isZero = if (amountTextField.isFiatValue) {
decimalFiatValue.isNullOrZero()
} else {
decimalCryptoValue.isNullOrZero()
}
val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
return prevState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
isPrimaryButtonEnabled = !isZero && !isCheckFailed,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
isError = isExceedBalance,
error = resourceReference(R.string.send_validation_amount_exceeds_balance).takeIf { isExceedBalance }
?: TextReference.EMPTY,
isError = isCheckFailed,
error = when {
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
isLessThanMinimumIfProvided -> {
val minimumAmount = minimumTransactionAmount
?.amount
?.format { crypto(cryptoCurrencyStatus.currency) }
.orEmpty()
resourceReference(
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
wrappedList(minimumAmount, minimumAmount),
)
}
else -> TextReference.EMPTY
},
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue),
imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),

View file

@ -1,53 +0,0 @@
package com.tangem.common.ui.amountScreen.converters.field
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import java.math.RoundingMode
/**
* Selects maximum amount value
*
* @property cryptoCurrencyStatus current cryptocurrency status
*/
class AmountFieldMaxAmountTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<AmountState> {
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
val amountTextField = prevState.amountTextField
val cryptoDecimals = amountTextField.cryptoAmount.decimals
val fiatDecimals = amountTextField.fiatAmount.decimals
val decimalCryptoValue = cryptoCurrencyStatus.value.amount
val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount
if (decimalCryptoValue.isNullOrZero()) return prevState
val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero()
val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty()
val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty()
return prevState.copy(
isPrimaryButtonEnabled = true,
amountTextField = amountTextField.copy(
isValuePasted = true,
value = cryptoValue,
fiatValue = fiatValue,
isError = false,
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
keyboardType = KeyboardType.Number,
),
),
)
}
}

View file

@ -0,0 +1,75 @@
package com.tangem.common.ui.amountScreen.converters.field
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.extensions.isZero
import com.tangem.utils.transformer.Transformer
import java.math.RoundingMode
/**
* Selects maximum amount value
*
* @property maxAmount maximum enter amount
*/
class AmountFieldSetMaxAmountTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val maxAmount: EnterAmountBoundary,
private val minAmount: EnterAmountBoundary?,
) : Transformer<AmountState> {
override fun transform(prevState: AmountState): AmountState {
if (prevState !is AmountState.Data) return prevState
val amountTextField = prevState.amountTextField
val cryptoDecimals = amountTextField.cryptoAmount.decimals
val fiatDecimals = amountTextField.fiatAmount.decimals
val decimalCryptoValue = maxAmount.amount
val decimalFiatValue = maxAmount.fiatAmount
if (decimalCryptoValue == null || decimalCryptoValue.isZero()) return prevState
val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals)
val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty()
val isLessThanMinimumIfProvided = minAmount?.amount?.let { decimalCryptoValue < it } ?: false
return prevState.copy(
isPrimaryButtonEnabled = !isLessThanMinimumIfProvided,
amountTextField = amountTextField.copy(
isValuePasted = true,
value = cryptoValue,
fiatValue = fiatValue,
isError = isLessThanMinimumIfProvided,
error = when {
isLessThanMinimumIfProvided -> {
val minimumAmount = minAmount
?.amount
?.format { crypto(cryptoCurrencyStatus.currency) }
.orEmpty()
resourceReference(
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
wrappedList(minimumAmount, minimumAmount),
)
}
else -> TextReference.EMPTY
},
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
keyboardOptions = KeyboardOptions(
imeAction = getKeyboardAction(isLessThanMinimumIfProvided, decimalCryptoValue),
keyboardType = KeyboardType.Number,
),
),
)
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.common.ui.amountScreen.models
import com.tangem.core.ui.extensions.TextReference
data class AmountParameters(
val title: TextReference,
val value: String,
)

View file

@ -13,8 +13,8 @@ sealed class AmountState {
/**
* @param isPrimaryButtonEnabled indicates if next state button enabled
* @param walletName user wallet name
* @param walletBalance user crypto currency balance in wallet
* @param title title
* @param availableBalance user crypto currency balance
* @param tokenIconState crypto currency icon state
* @param segmentedButtonConfig currency switcher config
* @param selectedButton selected currency index
@ -24,8 +24,8 @@ sealed class AmountState {
*/
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val walletName: String,
val walletBalance: TextReference,
val title: TextReference,
val availableBalance: TextReference,
val tokenIconState: CurrencyIconState,
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
val selectedButton: Int,

View file

@ -0,0 +1,22 @@
package com.tangem.common.ui.amountScreen.models
import java.math.BigDecimal
data class EnterAmountBoundary(
val amount: BigDecimal? = null,
val fiatAmount: BigDecimal? = null,
val fiatRate: BigDecimal? = null,
) {
constructor(
amount: BigDecimal? = null,
fiatRate: BigDecimal? = null,
) : this(
amount = amount,
fiatAmount = if (amount != null && fiatRate != null) {
amount * fiatRate
} else {
null
},
fiatRate = fiatRate,
)
}

View file

@ -17,8 +17,8 @@ object AmountStatePreviewData {
val amountState = AmountState.Data(
isPrimaryButtonEnabled = false,
walletName = "Family Wallet",
walletBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
title = stringReference("Family Wallet"),
availableBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
tokenIconState = CurrencyIconState.Loading,
segmentedButtonConfig = persistentListOf(
AmountSegmentedButtonsConfig(

View file

@ -19,16 +19,20 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.format.bigdecimal.anyDecimals
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import java.math.BigDecimal
@Composable
fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit) {
if (amountState !is AmountState.Data) return
val amount = amountState.amountTextField
val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
val cryptoAmount = formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol)
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount.fiatAmount.value,
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
@ -77,6 +81,9 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
}
}
fun formatWithSymbol(amount: String, symbol: String) =
BigDecimal.ZERO.format { crypto(symbol, 0).anyDecimals() }.replace("0", amount)
// region Preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -24,6 +24,8 @@ import com.tangem.core.ui.components.fields.AmountTextField
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.rememberDecimalFormat
@ -92,11 +94,7 @@ private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: Stri
),
) {
val text = if (amountField.isFiatValue) {
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = secondaryAmount.value,
cryptoCurrency = secondaryAmount.currencySymbol,
decimals = secondaryAmount.decimals,
)
secondaryAmount.value.format { crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals) }
} else {
BigDecimalFormatter.formatFiatAmount(
fiatAmount = secondaryAmount.value,
@ -140,7 +138,7 @@ private fun AmountFieldError(
exit = fadeOut(),
modifier = modifier,
) {
val errorText = remember(this) { error }
val errorText = remember(this, error) { error }
val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention
Text(
text = errorText.resolveReference(),

View file

@ -34,14 +34,14 @@ internal fun LazyListScope.amountField(
.background(TangemTheme.colors.background.action),
) {
Text(
text = amountState.walletName,
text = amountState.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing14),
)
val balance = amountState.walletBalance.orMaskWithStars(isBalanceHidden).resolveReference()
val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference()
AnimatedContent(
targetState = balance,
label = "Hide Balance Animation",

View file

@ -2,10 +2,10 @@ package com.tangem.common.ui.amountScreen.utils
import androidx.compose.ui.text.input.ImeAction
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.utils.isNullOrZero
import com.tangem.utils.extensions.isZero
import java.math.BigDecimal
import java.math.RoundingMode
@ -37,11 +37,11 @@ internal fun String.getFiatValue(
}
internal fun String.checkExceedBalance(
cryptoCurrencyStatus: CryptoCurrencyStatus,
maxEnterAmount: EnterAmountBoundary,
amountTextField: AmountFieldModel,
): Boolean {
val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO
val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
return if (amountTextField.isFiatValue) {
@ -51,8 +51,8 @@ internal fun String.checkExceedBalance(
}
}
internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) =
if (!isExceedBalance && !decimalCryptoValue.isNullOrZero()) {
internal fun getKeyboardAction(isCheckFailed: Boolean, decimalCryptoValue: BigDecimal) =
if (!isCheckFailed && !decimalCryptoValue.isZero()) {
ImeAction.Done
} else {
ImeAction.None

View file

@ -6,7 +6,9 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.shorted
import java.math.BigDecimal
sealed class NotificationUM(val config: NotificationConfig) {
@ -45,6 +47,14 @@ sealed class NotificationUM(val config: NotificationConfig) {
),
)
data class MinimumSendAmountError(val amount: String) : Error(
title = resourceReference(R.string.send_notification_invalid_amount_title),
subtitle = resourceReference(
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
wrappedList(amount, amount),
),
)
data class TransactionLimitError(
val cryptoCurrency: String,
val utxoLimit: String,
@ -267,8 +277,8 @@ sealed class NotificationUM(val config: NotificationConfig) {
subtitle = resourceReference(
R.string.koinos_insufficient_mana_to_send_koin_description,
formatArgs = wrappedList(
BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()),
BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()),
mana.format { crypto("", Blockchain.Koinos.decimals()).shorted() },
maxMana.format { crypto("", Blockchain.Koinos.decimals()).shorted() },
),
),
)
@ -286,11 +296,9 @@ sealed class NotificationUM(val config: NotificationConfig) {
subtitle = resourceReference(
R.string.koinos_mana_exceeds_koin_balance_description,
formatArgs = wrappedList(
BigDecimalFormatter.formatCryptoAmount(
availableKoinForTransfer,
Blockchain.Koinos.currency,
Blockchain.Koinos.decimals(),
),
availableKoinForTransfer.format {
crypto(Blockchain.Koinos.currency, Blockchain.Koinos.decimals())
},
),
),
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(

View file

@ -6,7 +6,9 @@ import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.utils.getFiatString
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.uncapped
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
@ -65,10 +67,23 @@ object NotificationsFactory {
if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) {
add(
NotificationUM.Error.ReserveAmount(
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = reserveAmount,
cryptoCurrency = cryptoCurrency,
),
reserveAmount.format {
crypto(cryptoCurrency)
},
),
)
}
}
fun MutableList<NotificationUM>.addMinimumAmountErrorNotification(
minimumSendAmount: BigDecimal?,
sendingAmount: BigDecimal,
cryptoCurrency: CryptoCurrency,
) {
if (minimumSendAmount != null && minimumSendAmount > sendingAmount) {
add(
NotificationUM.Error.MinimumSendAmountError(
amount = minimumSendAmount.format { crypto(cryptoCurrency) },
),
)
}
@ -87,10 +102,7 @@ object NotificationsFactory {
NotificationUM.Error.TransactionLimitError(
cryptoCurrency = cryptoCurrency.name,
utxoLimit = utxoLimit.maxLimit.toPlainString(),
amountLimit = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = utxoLimit.maxAmount,
cryptoCurrency = cryptoCurrency,
),
amountLimit = utxoLimit.maxAmount.format { crypto(cryptoCurrency) },
onConfirmClick = {
onReduceClick(
utxoLimit.maxAmount,
@ -124,10 +136,7 @@ object NotificationsFactory {
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
add(
NotificationUM.Error.ExistentialDeposit(
deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = existentialDeposit,
cryptoCurrency = cryptoCurrency,
),
deposit = existentialDeposit.format { crypto(cryptoCurrency).uncapped() },
onConfirmClick = {
onReduceClick(
existentialDeposit,
@ -155,10 +164,7 @@ object NotificationsFactory {
if (isFeeCoverage) {
add(
NotificationUM.Warning.FeeCoverageNotification(
cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = cryptoDiff,
cryptoCurrency = cryptoCurrency,
),
cryptoAmount = cryptoDiff.format { crypto(cryptoCurrency).uncapped() },
fiatAmount = getFiatString(
value = cryptoDiff,
rate = fiatRate,
@ -187,7 +193,7 @@ object NotificationsFactory {
if (isExceedsLimit) {
add(
NotificationUM.Error.MinimumAmountError(
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
amount = dustValue.format { crypto(cryptoCurrencyStatus.currency) },
),
)
}
@ -295,7 +301,7 @@ object NotificationsFactory {
dustValue?.let {
add(
NotificationUM.Error.MinimumAmountError(
amount = it.parseBigDecimal(sendingCurrency.decimals),
amount = it.format { crypto(sendingCurrency) },
),
)
}

View file

@ -1,9 +1,13 @@
package com.tangem.common.ui.tokens
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.YieldBalance
@ -25,16 +29,20 @@ import java.math.BigDecimal
*/
class TokenItemStateConverter(
private val appCurrency: AppCurrency,
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
CryptoCurrencyToIconStateConverter().convert(it)
},
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState,
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
createSubtitleState(it, appCurrency)
},
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
createFiatAmountState(it, appCurrency)
},
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
) : Converter<CryptoCurrencyStatus, TokenItemState> {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
return when (value.value) {
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
@ -53,7 +61,7 @@ class TokenItemStateConverter(
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
return TokenItemState.Loading(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
iconState = iconStateProvider(this),
titleState = titleStateProvider(this) as TokenItemState.TitleState.Content,
subtitleState = requireNotNull(subtitleStateProvider(this)),
)
@ -62,13 +70,10 @@ class TokenItemStateConverter(
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
return TokenItemState.Content(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
iconState = iconStateProvider(this),
titleState = titleStateProvider(this),
subtitleState = requireNotNull(subtitleStateProvider(this)),
fiatAmountState = TokenItemState.FiatAmountState.Content(
text = getFormattedFiatAmount(),
hasStaked = !getStakedBalance().isZero(),
),
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()),
onItemClick = { onItemClick(this) },
onItemLongClick = onItemLongClick?.let {
@ -80,23 +85,13 @@ class TokenItemStateConverter(
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
val amount = value.amount?.plus(getStakedBalance()) ?: return DASH_SIGN
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
return amount.format { crypto(currency) }
}
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
}
private fun CryptoCurrencyStatus.getStakedBalance() =
(value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero()
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState(): TokenItemState.Unreachable {
return TokenItemState.Unreachable(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
iconState = iconStateProvider(this),
titleState = titleStateProvider(this),
subtitleState = subtitleStateProvider(this),
onItemClick = { onItemClick(this) },
@ -109,7 +104,7 @@ class TokenItemStateConverter(
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState(): TokenItemState.NoAddress {
return TokenItemState.NoAddress(
id = currency.id.value,
iconState = iconStateConverter.convert(this),
iconState = iconStateProvider(this),
titleState = titleStateProvider(this),
subtitleState = subtitleStateProvider(this),
onItemLongClick = onItemLongClick?.let {
@ -118,9 +113,9 @@ class TokenItemStateConverter(
)
}
private companion object {
companion object {
fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
return when (val value = currencyStatus.value) {
is CryptoCurrencyStatus.Loading,
is CryptoCurrencyStatus.MissedDerivation,
@ -142,7 +137,7 @@ class TokenItemStateConverter(
}
}
fun createSubtitleState(
private fun createSubtitleState(
currencyStatus: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): TokenItemState.SubtitleState? {
@ -160,6 +155,29 @@ class TokenItemStateConverter(
}
}
private fun createFiatAmountState(
status: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): TokenItemState.FiatAmountState? {
return when (status.value) {
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.NoAccount,
-> {
TokenItemState.FiatAmountState.Content(
text = status.getFormattedFiatAmount(appCurrency),
hasStaked = !status.getStakedBalance().isZero(),
)
}
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Loading,
-> null
}
}
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
val fiatRate = value.fiatRate
val priceChange = value.priceChange
@ -167,10 +185,7 @@ class TokenItemStateConverter(
return if (fiatRate != null && priceChange != null) {
TokenItemState.SubtitleState.CryptoPriceContent(
price = fiatRate.getFormattedCryptoPrice(appCurrency),
priceChangePercent = BigDecimalFormatter.formatPercent(
percent = priceChange,
useAbsoluteValue = true,
),
priceChangePercent = priceChange.format { percent() },
type = priceChange.getPriceChangeType(),
)
} else {
@ -189,5 +204,15 @@ class TokenItemStateConverter(
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
return PriceChangeConverter.fromBigDecimal(value = this)
}
fun CryptoCurrencyStatus.getFormattedFiatAmount(appCurrency: AppCurrency): String {
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
}
private fun CryptoCurrencyStatus.getStakedBalance() =
(value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero()
}
}

View file

@ -78,8 +78,6 @@ sealed class Basic(
error = error,
)
class WalletOpened : Basic(event = "Wallet Opened")
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
event = "Request Support",
params = mapOf(

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExchangeProvider(
@Json(name = "id")
@ -26,6 +27,9 @@ data class ExchangeProvider(
@Json(name = "recommended")
val isRecommended: Boolean = false,
@Json(name = "slippage")
val slippage: BigDecimal?,
)
enum class ExchangeProviderType {

View file

@ -0,0 +1,178 @@
package com.tangem.datasource.api.onramp
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
internal class MockedOnrampApi : OnrampApi {
override suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>> = ApiResponse.Success(
COUNTRIES.map(OnrampCountryDTO::defaultCurrency),
)
override suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>> = ApiResponse.Success(COUNTRIES + RUSSIA)
override suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO> = ApiResponse.Success(RUSSIA)
override suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>> = ApiResponse.Success(
listOf(
PaymentMethodDTO(id = "google", name = "Google Play", image = ""),
PaymentMethodDTO(id = "apple", name = "Apple Pay", image = ""),
PaymentMethodDTO(id = "card", name = "Card", image = ""),
),
)
override suspend fun getPairs(body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>> = ApiResponse.Success(
listOf(
OnrampPairDTO(
fromCurrencyCode = "USD",
to = OnrampDestinationDTO(contractAddress = "0xcontract_address", network = "ethereum"),
providers = listOf(),
),
),
)
override suspend fun getQuote(
fromCurrencyCode: String,
toContractAddress: String,
toNetwork: String,
paymentMethod: String,
countryCode: String,
fromAmount: String,
toDecimals: Int,
providerId: String,
): ApiResponse<OnrampQuoteResponse> {
TODO("Not yet implemented")
}
override suspend fun getData(
fromCurrencyCode: String,
toContractAddress: String,
toNetwork: String,
paymentMethod: String,
countryCode: String,
fromAmount: String,
toDecimals: Int,
providerId: String,
toAddress: String,
redirectUrl: String,
language: String?,
theme: String?,
requestId: String,
): ApiResponse<OnrampDataResponse> {
TODO("Not yet implemented")
}
override suspend fun getStatus(txId: String): ApiResponse<OnrampStatusResponse> {
TODO("Not yet implemented")
}
private companion object {
private val RUSSIA = OnrampCountryDTO(
name = "Russia",
code = "RU",
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
alpha3 = "RUS",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "Russian ruble",
code = "RUB",
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
precision = 2,
),
onrampAvailable = false,
)
private val COUNTRIES = listOf(
OnrampCountryDTO(
name = "United States of America",
code = "USA",
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
alpha3 = "USA",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "US Dollar",
code = "USD",
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
precision = 2,
),
onrampAvailable = true,
),
OnrampCountryDTO(
name = "Europe Union",
code = "EU",
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
alpha3 = "EUR",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "Euro",
code = "EUR",
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
precision = 2,
),
onrampAvailable = true,
),
OnrampCountryDTO(
name = "Great Britain",
code = "GB",
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
alpha3 = "GB",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "British Pound Sterling",
code = "GBP",
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
precision = 2,
),
onrampAvailable = true,
),
OnrampCountryDTO(
name = "CANADA",
code = "CA",
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
alpha3 = "CA",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "Canadian Dollar",
code = "CAD",
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
precision = 2,
),
onrampAvailable = true,
),
OnrampCountryDTO(
name = "Hon Kong",
code = "HK",
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
alpha3 = "HK",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "Hon Kong Dollar",
code = "HKD",
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
precision = 2,
),
onrampAvailable = true,
),
OnrampCountryDTO(
name = "Australia",
code = "AU",
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
alpha3 = "AU",
continent = "",
defaultCurrency = OnrampCurrencyDTO(
name = "Australian Dollar",
code = "AUD",
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
precision = 2,
),
onrampAvailable = true,
),
)
}
}

View file

@ -3,7 +3,8 @@ package com.tangem.datasource.api.stakekit
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.stakekit.models.request.*
import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse
import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse
import com.tangem.datasource.api.stakekit.models.response.ActionDTO
import com.tangem.datasource.api.stakekit.models.response.GetActionsResponse
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
@ -35,14 +36,23 @@ interface StakeKitApi {
@Body body: YieldBalanceRequestBody,
): ApiResponse<List<BalanceDTO>>
@GET("actions")
suspend fun getActions(
@Query("walletAddress") walletAddress: String,
@Query("network") network: String,
@Query("status") status: String,
@Query("sort") sort: String = "createdAtDesc",
@Query("limit") limit: Int = 50,
): ApiResponse<GetActionsResponse>
@POST("actions/enter")
suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/exit")
suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/pending")
suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/enter/estimate-gas")
suspend fun estimateGasOnEnter(@Body body: ActionRequestBody): ApiResponse<StakingGasEstimateDTO>

View file

@ -9,7 +9,7 @@ import org.joda.time.DateTime
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class EnterActionResponse(
data class ActionDTO(
@Json(name = "id")
val id: String,
@Json(name = "integrationId")

View file

@ -0,0 +1,16 @@
package com.tangem.datasource.api.stakekit.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GetActionsResponse(
@Json(name = "data")
val data: List<ActionDTO>,
@Json(name = "hasNextPage")
val hasNextPage: Boolean,
@Json(name = "limit")
val limit: Int,
@Json(name = "page")
val page: Int,
)

View file

@ -7,33 +7,33 @@ import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class YieldDTO(
@Json(name = "id")
val id: String,
val id: String?,
@Json(name = "token")
val token: TokenDTO,
val token: TokenDTO?,
@Json(name = "tokens")
val tokens: List<TokenDTO>,
val tokens: List<TokenDTO>?,
@Json(name = "args")
val args: ArgsDTO,
val args: ArgsDTO?,
@Json(name = "status")
val status: StatusDTO,
val status: StatusDTO?,
@Json(name = "apy")
val apy: BigDecimal,
val apy: BigDecimal?,
@Json(name = "rewardRate")
val rewardRate: Double,
val rewardRate: Double?,
@Json(name = "rewardType")
val rewardType: RewardTypeDTO,
val rewardType: RewardTypeDTO?,
@Json(name = "metadata")
val metadata: MetadataDTO,
val metadata: MetadataDTO?,
@Json(name = "validators")
val validators: List<ValidatorDTO>,
val validators: List<ValidatorDTO>?,
@Json(name = "isAvailable")
val isAvailable: Boolean,
val isAvailable: Boolean?,
) {
@JsonClass(generateAdapter = true)
data class StatusDTO(
@Json(name = "enter")
val enter: Boolean,
val enter: Boolean?,
@Json(name = "exit")
val exit: Boolean?,
)
@ -41,21 +41,21 @@ data class YieldDTO(
@JsonClass(generateAdapter = true)
data class ArgsDTO(
@Json(name = "enter")
val enter: Enter,
val enter: Enter?,
@Json(name = "exit")
val exit: Enter?,
) {
@JsonClass(generateAdapter = true)
data class Enter(
@Json(name = "addresses")
val addresses: Addresses,
val addresses: Addresses?,
@Json(name = "args")
val args: Map<String, AddressArgumentDTO>,
val args: Map<String, AddressArgumentDTO>?,
) {
@JsonClass(generateAdapter = true)
data class Addresses(
@Json(name = "address")
val address: AddressArgumentDTO,
val address: AddressArgumentDTO?,
@Json(name = "additionalAddresses")
val additionalAddresses: Map<String, AddressArgumentDTO>? = null,
)
@ -65,11 +65,11 @@ data class YieldDTO(
@JsonClass(generateAdapter = true)
data class ValidatorDTO(
@Json(name = "address")
val address: String,
val address: String?,
@Json(name = "status")
val status: ValidatorStatusDTO,
val status: ValidatorStatusDTO?,
@Json(name = "name")
val name: String,
val name: String?,
@Json(name = "image")
val image: String?,
@Json(name = "website")
@ -83,7 +83,7 @@ data class YieldDTO(
@Json(name = "votingPower")
val votingPower: Double?,
@Json(name = "preferred")
val preferred: Boolean,
val preferred: Boolean?,
) {
@JsonClass(generateAdapter = true)
enum class ValidatorStatusDTO {
@ -106,51 +106,51 @@ data class YieldDTO(
@JsonClass(generateAdapter = true)
data class MetadataDTO(
@Json(name = "name")
val name: String,
val name: String?,
@Json(name = "logoURI")
val logoUri: String,
val logoUri: String?,
@Json(name = "description")
val description: String,
val description: String?,
@Json(name = "documentation")
val documentation: String?,
@Json(name = "gasFeeToken")
val gasFeeTokenDTO: TokenDTO,
val gasFeeTokenDTO: TokenDTO?,
@Json(name = "token")
val tokenDTO: TokenDTO,
val tokenDTO: TokenDTO?,
@Json(name = "tokens")
val tokensDTO: List<TokenDTO>,
val tokensDTO: List<TokenDTO>?,
@Json(name = "type")
val type: String,
val type: String?,
@Json(name = "rewardSchedule")
val rewardSchedule: RewardScheduleDTO,
val rewardSchedule: RewardScheduleDTO?,
@Json(name = "cooldownPeriod")
val cooldownPeriod: PeriodDTO?,
@Json(name = "warmupPeriod")
val warmupPeriod: PeriodDTO,
val warmupPeriod: PeriodDTO?,
@Json(name = "rewardClaiming")
val rewardClaiming: RewardClaimingDTO,
val rewardClaiming: RewardClaimingDTO?,
@Json(name = "defaultValidator")
val defaultValidator: String?,
@Json(name = "minimumStake")
val minimumStake: Int?,
@Json(name = "supportsMultipleValidators")
val supportsMultipleValidators: Boolean,
val supportsMultipleValidators: Boolean?,
@Json(name = "revshare")
val revshare: EnabledDTO,
val revshare: EnabledDTO?,
@Json(name = "fee")
val fee: EnabledDTO,
val fee: EnabledDTO?,
) {
@JsonClass(generateAdapter = true)
data class PeriodDTO(
@Json(name = "days")
val days: Int,
val days: Int?,
)
@JsonClass(generateAdapter = true)
data class EnabledDTO(
@Json(name = "enabled")
val enabled: Boolean,
val enabled: Boolean?,
)
enum class RewardScheduleDTO {

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.onramp.MockedOnrampApi
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
@ -98,22 +99,24 @@ internal object NetworkModule {
@Provides
@Singleton
fun provideOnrampApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
apiConfigsManager: ApiConfigsManager,
appLogsStore: AppLogsStore,
// @NetworkMoshi moshi: Moshi,
// @ApplicationContext context: Context,
// apiConfigsManager: ApiConfigsManager,
// appLogsStore: AppLogsStore,
): OnrampApi {
return createApi(
id = ApiConfig.ID.Express,
moshi = moshi,
context = context,
apiConfigsManager = apiConfigsManager,
clientBuilder = {
addInterceptor(
NetworkLogsSaveInterceptor(appLogsStore),
)
},
)
// TODO: Remove when backend will be ready - [REDACTED_TASK_KEY]
return MockedOnrampApi()
// return createApi(
// id = ApiConfig.ID.Express,
// moshi = moshi,
// context = context,
// apiConfigsManager = apiConfigsManager,
// clientBuilder = {
// addInterceptor(
// NetworkLogsSaveInterceptor(appLogsStore),
// )
// },
// )
}
@Provides

Some files were not shown because too many files have changed in this diff Show more