Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-23 14:37:50 +03:00
commit 2f84def7cc
1244 changed files with 20573 additions and 7307 deletions

View file

@ -39,8 +39,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory
fun getOnrampFeatureToggles(): OnrampFeatureToggles
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
fun getOnboardingRepository(): OnboardingRepository
@ -141,8 +137,6 @@ interface ApplicationEntryPoint {
fun getWorkerFactory(): HiltWorkerFactory
fun getOnlineCardVerifier(): OnlineCardVerifier
fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory
fun getApiConfigsManager(): ApiConfigsManager

View file

@ -26,17 +26,13 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.common.routing.deeplink.DeeplinkConst.WEBLINK_KEY
import com.tangem.common.routing.deeplink.PayloadToDeeplinkConverter
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.WEBLINK_KEY
import com.tangem.core.deeplink.converter.PayloadToDeeplinkConverter
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.UiDependencies
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode
@ -53,6 +49,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.tester.api.TesterMenuLauncher
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.google.GoogleServicesHelper
import com.tangem.operations.backup.BackupService
@ -69,7 +66,6 @@ import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandle
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.configurator.AppRouterConfig
@ -103,9 +99,6 @@ val mainScope = CoroutineScope(mainCoroutineContext)
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
lateinit var appStateHolder: AppStateHolder
/** Router for opening tester menu */
@Inject
lateinit var cardSdkOwner: CardSdkOwner
@ -122,9 +115,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
lateinit var walletConnectInteractor: WalletConnectInteractor
@Inject
lateinit var deepLinksRegistry: DeepLinksRegistry
@Inject
lateinit var settingsRepository: SettingsRepository
@ -143,9 +133,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
lateinit var userWalletsListManager: UserWalletsListManager
@Inject
lateinit var emailSender: EmailSender
@Inject
@RootAppComponentContext
internal lateinit var rootComponentContext: AppComponentContext
@ -174,15 +161,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
lateinit var dispatchers: CoroutineDispatcherProvider
@Inject
internal lateinit var uiDependencies: UiDependencies
@Inject
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
@Inject
internal lateinit var routingFeatureToggle: RoutingFeatureToggle
@Inject
internal lateinit var deeplinkFactory: DeepLinkFactory
@ -192,6 +173,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
internal lateinit var urlOpener: UrlOpener
@Inject
internal lateinit var testerMenuLauncher: TesterMenuLauncher
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -244,13 +228,12 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
sendStakingUnsubmittedHashes()
checkGoogleServicesAvailability()
if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) {
// handle intent only on start, not on recreate
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
lifecycle.addObserver(WindowObscurationObserver)
lifecycle.addObserver(defaultDeviceFlipDetector)
if (BuildConfig.TESTER_MENU_ENABLED) {
lifecycle.addObserver(testerMenuLauncher.launchOnShakeObserver)
}
}
private fun setRootContent() {
@ -481,7 +464,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
}
if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) {
if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
@ -489,26 +472,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) {
if (routingFeatureToggle.isDeepLinkNavigationEnabled) {
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
val webLink = intent.getStringExtra(WEBLINK_KEY)
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
val webLink = intent.getStringExtra(WEBLINK_KEY)
val receivedDeepLink = intent.data ?: deepLinkExtras
val receivedDeepLink = intent.data ?: deepLinkExtras
when {
receivedDeepLink != null -> {
deeplinkFactory.handleDeeplink(
deeplinkUri = receivedDeepLink,
coroutineScope = lifecycleScope,
isFromOnNewIntent = isFromOnNewIntent,
)
}
webLink?.uriValidate() == true -> {
urlOpener.openUrl(webLink)
}
when {
receivedDeepLink != null -> {
deeplinkFactory.handleDeeplink(
deeplinkUri = receivedDeepLink,
coroutineScope = lifecycleScope,
isFromOnNewIntent = isFromOnNewIntent,
)
}
webLink?.uriValidate() == true -> {
urlOpener.openUrl(webLink)
}
} else {
deepLinksRegistry.launch(intent)
}
}

View file

@ -58,8 +58,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.attestation.api.TangemApiServiceSettings
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -79,7 +77,6 @@ import com.tangem.wallet.BuildConfig
import dagger.hilt.EntryPoints
import kotlinx.coroutines.*
import org.rekotlin.Store
import kotlin.collections.set
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
lateinit var store: Store<AppState>
@ -186,9 +183,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val transactionSignerFactory: TransactionSignerFactory
get() = entryPoint.getTransactionSignerFactory()
private val onrampFeatureToggles: OnrampFeatureToggles
get() = entryPoint.getOnrampFeatureToggles()
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
get() = entryPoint.getOnboardingV2FeatureToggles()
@ -224,9 +218,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
.setWorkerFactory(workerFactory)
.build()
private val onlineCardVerifier: OnlineCardVerifier
get() = entryPoint.getOnlineCardVerifier()
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory
get() = entryPoint.getColdUserWalletBuilderFactory()
@ -363,7 +354,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
shareManager = shareManager,
appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
onboardingRepository = onboardingRepository,
@ -372,7 +362,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
clipboardManager = clipboardManager,
settingsManager = settingsManager,
uiMessageSender = uiMessageSender,
onlineCardVerifier = onlineCardVerifier,
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
userTokensResponseStore = userTokensResponseStore,
),

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
@ -21,6 +22,15 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
}
fun Analytics.setContext(userWallet: UserWallet) {
setUserId(userWallet.walletId.stringValue)
// TODO add product type for hot ([REDACTED_TASK_KEY])
if (userWallet is UserWallet.Cold) {
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
}
}
/**
* Erases the context
*/

View file

@ -4,8 +4,8 @@ import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError

View file

@ -3,16 +3,11 @@ package com.tangem.tap.common.extensions
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.common.services.Result
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletAddressData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import kotlinx.coroutines.delay
@ -57,43 +52,4 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<Wallet> = try
}
}
}
}
internal fun WalletManager.getTopUpUrl(cryptoCurrency: CryptoCurrency): String? {
val globalState = store.state.globalState
val defaultAddress = wallet.address
return globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
cryptoCurrency = cryptoCurrency,
fiatCurrencyName = globalState.appCurrency.code,
walletAddress = defaultAddress,
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
)
}
internal fun WalletManager?.getAddressData(): WalletAddressData? {
val wallet = this?.wallet ?: return null
val addressDataList = wallet.createAddressesData()
return if (addressDataList.isEmpty()) null else addressDataList[0]
}
private fun Wallet.createAddressesData(): List<WalletAddressData> {
val listOfAddressData = mutableListOf<WalletAddressData>()
// put a defaultAddress at the first place
addresses.forEach {
val addressData = WalletAddressData(
it.value,
it.type,
getShareUri(it.value),
getExploreUrl(it.value),
)
if (it.type == AddressType.Default) {
listOfAddressData.add(0, addressData)
} else {
listOfAddressData.add(addressData)
}
}
return listOfAddressData
}

View file

@ -6,6 +6,7 @@ import android.util.Log
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.util.Logger
@ -36,6 +37,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
} else {
add(GifDecoder.Factory())
}
add(SvgDecoder.Factory())
}
.memoryCachePolicy(CachePolicy.ENABLED)
.memoryCache {

View file

@ -30,21 +30,5 @@ sealed class GlobalAction : Action {
data class Success(val appCurrency: AppCurrency) : GlobalAction()
}
data class UpdateWalletSignedHashes(
val walletSignedHashes: Int?,
val remainingSignatures: Int?,
val walletPublicKey: ByteArray,
) : GlobalAction()
data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction()
object ExchangeManager : GlobalAction() {
object Init : GlobalAction() {
data class Success(
val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager,
) : GlobalAction()
}
object Update : GlobalAction()
}
}

View file

@ -2,23 +2,14 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.LogConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.network.exchangeServices.CardExchangeRules
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
@ -31,17 +22,17 @@ object GlobalMiddleware {
val handler = globalMiddlewareHandler
}
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
private val globalMiddlewareHandler: Middleware<AppState> = { _, _ ->
{ nextDispatch ->
{ action ->
handleAction(action, appState)
handleAction(action)
nextDispatch(action)
}
}
}
@Suppress("LongMethod", "ComplexMethod")
private fun handleAction(action: Action, appState: () -> AppState?) {
private fun handleAction(action: Action) {
when (action) {
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
when (action.result) {
@ -51,42 +42,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
}
}
}
is GlobalAction.RestoreAppCurrency -> {
restoreAppCurrency()
}
is GlobalAction.ExchangeManager.Init -> {
val config = store.inject(DaggerGraphState::environmentConfigStorage).getConfigSync()
scope.launch {
val scanResponseProvider: () -> ScanResponse? = {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY]
}
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
val buyService = makeBuyExchangeService(config)
val sellService = makeSellExchangeService(config)
val exchangeManager = CurrencyExchangeManager(
buyService = buyService,
sellService = sellService,
primaryRules = CardExchangeRules(cardProvider),
)
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
store.inject(DaggerGraphState::appStateHolder).buyService = buyService
store.inject(DaggerGraphState::appStateHolder).sellService = sellService
store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
}
}
is GlobalAction.ExchangeManager.Init.Success -> {}
is GlobalAction.ExchangeManager.Update -> {
val exchangeManager = appState()?.globalState?.exchangeManager.guard {
store.dispatchDebugErrorNotification("exchangeManager is not initialized")
return
}
scope.launch { exchangeManager.update() }
}
is GlobalAction.RestoreAppCurrency -> restoreAppCurrency()
}
}
@ -119,21 +75,4 @@ private fun restoreAppCurrency() {
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency))
}
}
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
return MoonPayService(
apiKey = environmentConfig.moonPayApiKey,
secretKey = environmentConfig.moonPayApiSecretKey,
logEnabled = LogConfig.network.moonPayService,
)
}
private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
return MercuryoService(
environment = MercuryoEnvironment.prod(
widgetId = environmentConfig.mercuryoWidgetId,
secret = environmentConfig.mercuryoSecret,
),
)
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action
@Suppress("LongMethod", "ComplexMethod")
@ -26,24 +25,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.RestoreAppCurrency.Success -> {
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanResponse?.card ?: return globalState
val wallet = card.wallets
.firstOrNull { it.publicKey.contentEquals(action.walletPublicKey) }
?: return globalState
val newCardInstance = card.copy(
wallets = card.wallets.toMutableList().also { walletsMutable ->
walletsMutable.replaceBy(
item = wallet.copy(
totalSignedHashes = action.walletSignedHashes,
remainingSignatures = action.remainingSignatures,
),
) { it.index == wallet.index }
},
)
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
}
is GlobalAction.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing)
is GlobalAction.ShowDialog -> {
globalState.copy(dialog = action.stateDialog)
@ -51,9 +32,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.HideDialog -> {
globalState.copy(dialog = null)
}
is GlobalAction.ExchangeManager.Init.Success -> {
globalState.copy(exchangeManager = action.exchangeManager)
}
else -> globalState
}
}

View file

@ -4,8 +4,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType
data class GlobalState(
@ -16,7 +14,6 @@ data class GlobalState(
val appCurrency: AppCurrency = AppCurrency.Default,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
val isLastSignWithRing: Boolean = false,
) : StateType
@ -24,6 +21,5 @@ typealias CryptoCurrencyName = String
data class OnboardingState(
val onboardingStarted: Boolean = false,
val onboardingManager: OnboardingManager? = null,
val shouldResetOnCreate: Boolean = false,
)

View file

@ -1,8 +1,8 @@
package com.tangem.tap.common.redux.legacy
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
@ -37,8 +37,7 @@ internal object LegacyMiddleware {
)
store.dispatchWithMain(
DetailsAction.PrepareScreen(
// TODO [REDACTED_TASK_KEY]
scanResponse = selectedUserWallet.requireColdWallet().scanResponse,
scanResponse = (selectedUserWallet as? UserWallet.Cold)?.scanResponse,
initializedAppSettingsState = initializedAppSettingsStateContent,
),
)
@ -67,7 +66,7 @@ internal object LegacyMiddleware {
?: AppThemeMode.DEFAULT,
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
.getBalanceHidingSettings().isHidingEnabledInSettings,
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false,
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
)
}
}

View file

@ -16,16 +16,13 @@ import com.tangem.crypto.bip39.Wordlist
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager
import com.tangem.datasource.utils.AddHeadersInterceptor
import com.tangem.datasource.utils.RequestHeader
import com.tangem.operations.attestation.api.TangemApiServiceSettings
import com.tangem.sdk.DefaultSessionViewDelegate
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
import com.tangem.sdk.extensions.*
import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
import com.tangem.sdk.nfc.NfcManager
@ -34,13 +31,6 @@ import com.tangem.tap.foregroundActivityObserver
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import com.tangem.wallet.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton
@ -55,11 +45,9 @@ import javax.inject.Singleton
internal class DefaultCardSdkProvider @Inject constructor(
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
private val apiConfigsManager: ApiConfigsManager,
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
appPreferencesStore: AppPreferencesStore,
) : CardSdkProvider, CardSdkOwner {
private val observer = Observer()
@ -70,26 +58,15 @@ internal class DefaultCardSdkProvider @Inject constructor(
get() = holder?.sdk ?: tryToRegisterWithForegroundActivity()
init {
if (BuildConfig.TESTER_MENU_ENABLED) {
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
.map {
when (it[ApiConfig.ID.Attestation.name]) {
ApiEnvironment.DEV,
ApiEnvironment.STAGE,
-> false
ApiEnvironment.PROD,
null,
-> true
}
val mutableManager = apiConfigsManager as? MutableApiConfigsManager
mutableManager?.addListener(
object : MutableApiConfigsManager.ApiConfigEnvChangeListener(id = ApiConfig.ID.TangemTech) {
override fun onChange(environmentConfig: ApiEnvironmentConfig) {
holder?.sdk?.config?.tangemApiBaseUrl = environmentConfig.baseUrl
}
.distinctUntilChanged()
.onEach { isProd ->
holder?.let {
it.sdk.config.isTangemAttestationProdEnv = isProd
}
}
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.main))
}
},
)
TangemApiServiceSettings.addInterceptors(
AddHeadersInterceptor(
@ -187,10 +164,8 @@ internal class DefaultCardSdkProvider @Inject constructor(
keystoreManager = keystoreManager,
wordlist = Wordlist.getWordlist(activity),
config = config.apply {
isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled
val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.Attestation)
isTangemAttestationProdEnv = apiConfig.environment == ApiEnvironment.PROD
val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech)
tangemApiBaseUrl = apiConfig.baseUrl
},
)

View file

@ -2,9 +2,9 @@ package com.tangem.tap.data
import com.tangem.common.CompletionResult
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull

View file

@ -9,7 +9,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.network.exchangeServices.DefaultRampManager
@ -51,17 +50,13 @@ internal object ActivityModule {
currenciesRepository: CurrenciesRepository,
excludedBlockchains: ExcludedBlockchains,
dispatchers: CoroutineDispatcherProvider,
onrampFeatureToggles: OnrampFeatureToggles,
): RampStateManager {
return DefaultRampManager(
exchangeService = appStateHolder.exchangeService,
buyService = Provider { requireNotNull(appStateHolder.buyService) },
sellService = Provider { requireNotNull(appStateHolder.sellService) },
expressServiceLoader = expressServiceLoader,
currenciesRepository = currenciesRepository,
excludedBlockchains = excludedBlockchains,
dispatchers = dispatchers,
onrampFeatureToggles = onrampFeatureToggles,
excludedBlockchains = excludedBlockchains,
)
}

View file

@ -7,8 +7,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -71,22 +69,18 @@ internal object ManageTokensDomainModule {
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveManagedTokensUseCase {
return SaveManagedTokensUseCase(
customTokensRepository = customTokensRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -10,8 +10,6 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import dagger.Module
@ -62,21 +60,17 @@ object MarketsDomainModule {
derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository,
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveMarketTokensUseCase {
return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository,
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -5,6 +5,8 @@ import com.tangem.domain.nft.*
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -33,9 +35,13 @@ internal object NFTDomainModule {
fun providesFetchNFTCollectionsUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
@Provides
@ -43,9 +49,13 @@ internal object NFTDomainModule {
fun providesRefreshAllNFTUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
@Provides
@ -118,8 +128,16 @@ internal object NFTDomainModule {
walletsRepository: WalletsRepository,
nftRepository: NFTRepository,
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): DisableWalletNFTUseCase {
return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository)
return DisableWalletNFTUseCase(
walletsRepository = walletsRepository,
nftRepository = nftRepository,
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@Provides

View file

@ -94,12 +94,10 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideFetchStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
)

View file

@ -3,10 +3,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.swap.SwapErrorResolver
import com.tangem.domain.swap.SwapRepositoryV2
import com.tangem.domain.swap.SwapTransactionRepository
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
import com.tangem.domain.swap.usecase.*
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
import dagger.Module
import dagger.Provides
@ -73,4 +70,30 @@ internal object SwapDomainModule {
swapErrorResolver = swapErrorResolver,
)
}
@Provides
@Singleton
fun provideGetSwapDataUseCase(
swapRepositoryV2: SwapRepositoryV2,
swapErrorResolver: SwapErrorResolver,
): GetSwapDataUseCase {
return GetSwapDataUseCase(
swapRepositoryV2 = swapRepositoryV2,
swapErrorResolver = swapErrorResolver,
)
}
@Provides
@Singleton
fun provideSwapTransactionSentUseCase(
swapRepositoryV2: SwapRepositoryV2,
swapTransactionRepository: SwapTransactionRepository,
swapErrorResolver: SwapErrorResolver,
): SwapTransactionSentUseCase {
return SwapTransactionSentUseCase(
swapRepositoryV2 = swapRepositoryV2,
swapTransactionRepository = swapTransactionRepository,
swapErrorResolver = swapErrorResolver,
)
}
}

View file

@ -22,6 +22,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,18 +41,18 @@ internal object TokensDomainModule {
@Singleton
fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -60,19 +61,15 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchTokenListUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchTokenListUseCase {
return FetchTokenListUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -107,8 +104,15 @@ internal object TokensDomainModule {
fun provideRemoveCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RemoveCurrencyUseCase {
return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade)
return RemoveCurrencyUseCase(
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@Provides
@ -145,6 +149,8 @@ internal object TokensDomainModule {
currencyChecksRepository: CurrencyChecksRepository,
dispatchers: CoroutineDispatcherProvider,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade,
@ -152,6 +158,8 @@ internal object TokensDomainModule {
dispatchers = dispatchers,
currencyChecksRepository = currencyChecksRepository,
currencyStatusOperations = baseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -159,18 +167,18 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -179,26 +187,26 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchCardTokenListUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@Provides
@Singleton
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
return GetCryptoCurrencyUseCase(currenciesRepository)
fun provideGetCryptoCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCryptoCurrencyUseCase {
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles)
}
@Provides
@ -219,9 +227,16 @@ internal object TokensDomainModule {
@Singleton
fun provideApplyTokenListSortingUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers)
return ApplyTokenListSortingUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
}
@Provides
@ -280,9 +295,13 @@ internal object TokensDomainModule {
@Singleton
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): IsCryptoCurrencyCoinCouldHideUseCase {
return IsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -300,9 +319,16 @@ internal object TokensDomainModule {
@Singleton
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): GetBalanceNotEnoughForFeeWarningUseCase {
return GetBalanceNotEnoughForFeeWarningUseCase(currenciesRepository, dispatchers)
return GetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
}
@Provides
@ -343,10 +369,14 @@ internal object TokensDomainModule {
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository: CurrenciesRepository,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshMultiCurrencyWalletQuotesUseCase {
return RefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository = currenciesRepository,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -374,6 +404,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
): BaseCurrenciesStatusesOperations {
return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
@ -388,6 +419,7 @@ internal object TokensDomainModule {
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
)
}
@ -406,6 +438,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
): BaseCurrencyStatusOperations {
return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
@ -419,6 +452,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -428,4 +462,26 @@ internal object TokensDomainModule {
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
return GetCryptoCurrenciesUseCase(currenciesRepository)
}
@Provides
@Singleton
fun provideWalletBalanceFetcher(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
dispatchers: CoroutineDispatcherProvider,
): WalletBalanceFetcher {
return WalletBalanceFetcher(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
dispatchers = dispatchers,
)
}
}

View file

@ -4,11 +4,14 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.hot.TangemHotWalletSigner
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -41,6 +44,7 @@ internal object TransactionDomainModule {
transactionRepository: TransactionRepository,
walletManagersFacade: WalletManagersFacade,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
): SendTransactionUseCase {
return SendTransactionUseCase(
demoConfig = DemoConfig(),
@ -48,6 +52,7 @@ internal object TransactionDomainModule {
transactionRepository = transactionRepository,
walletManagersFacade = walletManagersFacade,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
getHotWalletSigner = tangemHotWalletSignerFactory::create,
)
}
@ -58,12 +63,16 @@ internal object TransactionDomainModule {
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): AssociateAssetUseCase {
return AssociateAssetUseCase(
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -15,10 +15,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.features.nft.NFTFeatureToggles
import com.tangem.operations.attestation.CardArtworksProvider
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -191,28 +188,14 @@ internal object WalletsDomainModule {
@Provides
@Singleton
fun provideGetCardImageUseCase(
onlineCardVerifier: OnlineCardVerifier,
cardArtworksProvider: CardArtworksProvider,
cardSdkFeatureToggles: CardSdkFeatureToggles,
): GetCardImageUseCase {
return GetCardImageUseCase(
verifier = onlineCardVerifier,
cardArtworksProvider = cardArtworksProvider,
cardSdkFeatureToggles = cardSdkFeatureToggles,
)
fun provideGetCardImageUseCase(cardArtworksProvider: CardArtworksProvider): GetCardImageUseCase {
return GetCardImageUseCase(cardArtworksProvider = cardArtworksProvider)
}
@Provides
@Singleton
fun providesIsWalletNFTEnabledSyncUseCase(
walletsRepository: WalletsRepository,
nftFeatureToggles: NFTFeatureToggles,
): IsWalletNFTEnabledSyncUseCase {
return IsWalletNFTEnabledSyncUseCase(
walletsRepository = walletsRepository,
nftFeatureToggles = nftFeatureToggles,
)
fun providesIsWalletNFTEnabledSyncUseCase(walletsRepository: WalletsRepository): IsWalletNFTEnabledSyncUseCase {
return IsWalletNFTEnabledSyncUseCase(walletsRepository = walletsRepository)
}
@Provides

View file

@ -0,0 +1,24 @@
package com.tangem.tap.di.hot
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.tap.domain.hot.HotWalletPasswordRequester
import com.tangem.tap.features.hot.DefaultHotWalletPasswordRequester
import com.tangem.tap.features.hot.TangemHotSDKProxy
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemHotSdkModule {
@Binds
@Singleton
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
@Binds
@Singleton
fun bindHotWalletPasswordRequester(impl: DefaultHotWalletPasswordRequester): HotWalletPasswordRequester
}

View file

@ -1,9 +1,7 @@
package com.tangem.tap.di.routing
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.tap.routing.ProxyAppRouter
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
@ -33,10 +31,4 @@ internal object AppRouterModule {
@Provides
@Singleton
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
@Provides
@Singleton
fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle {
return RoutingFeatureToggle(featureTogglesManager)
}
}

View file

@ -4,8 +4,7 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.store
@ -35,14 +34,15 @@ class TapWalletManager(
}
private suspend fun loadUserWalletData(userWallet: UserWallet) {
Analytics.setContext(userWallet.requireColdWallet().scanResponse) // [REDACTED_TASK_KEY]
val scanResponse = userWallet.scanResponse
Analytics.setContext(userWallet)
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
withMainContext {
// Order is important
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
if (userWallet is UserWallet.Cold) {
val scanResponse = userWallet.scanResponse
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
withMainContext {
// Order is important
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
}
}
}
}

View file

@ -17,9 +17,9 @@ import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
@ -50,7 +50,7 @@ internal class DefaultDerivationsRepository(
networkFactory.create(
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
extraDerivationPath = null,
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
userWallet = userWallet,
)
},
)
@ -61,7 +61,11 @@ internal class DefaultDerivationsRepository(
userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
}
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
if (userWallet is UserWallet.Hot) {
return
}
userWallet.requireColdWallet()
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
Timber.d("Nothing to derive")
@ -84,14 +88,18 @@ internal class DefaultDerivationsRepository(
): Boolean {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
if (userWallet is UserWallet.Hot) {
return false
}
val derivations =
MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY]
MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse)
.findByNetworks(
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
networkFactory.create(
blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null,
extraDerivationPath = extraDerivationPath,
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
userWallet = userWallet,
)
},
)

View file

@ -13,7 +13,7 @@ import com.tangem.common.core.UserCodeRequestPolicy
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.domain.models.wallet.UserWalletId
import com.tangem.sdk.api.TangemSdkManager
internal class DefaultResetCardUseCase(

View file

@ -1,47 +0,0 @@
package com.tangem.tap.domain.extensions
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.models.Artwork
import com.tangem.operations.attestation.CardArtworksProvider
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
fun CardDTO.signedHashesCount(): Int {
return wallets.sumOf { it.totalSignedHashes ?: 0 }
}
suspend fun CardDTO.getOrLoadCardArtworkUrl(
cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null,
onlineCardVerifier: OnlineCardVerifier,
): String {
fun ifAnyError(): String {
return when {
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
else -> {
when (getTwinCardNumber()) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
else -> Artwork.DEFAULT_IMG_URL
}
}
}
}
return when (val cardInfoResult = cardInfo ?: onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = cardInfoResult.data.artwork?.id
if (artworkId.isNullOrEmpty()) {
ifAnyError()
} else {
CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
}
is Result.Failure -> ifAnyError()
}
}

View file

@ -0,0 +1,113 @@
package com.tangem.tap.domain.hot
import com.tangem.common.core.TangemSdkError
import com.tangem.features.hotwallet.HotWalletPasswordRequester
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.exception.WrongPasswordException
import com.tangem.hot.sdk.model.*
import javax.inject.Inject
class HotWalletAccessor @Inject constructor(
private val tangemHotSdk: TangemHotSdk,
private val hotWalletPasswordRequester: HotWalletPasswordRequester,
) {
suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List<DataToSign>): List<SignedData> {
val auth = when (hotWalletId.authType) {
HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth
HotWalletId.AuthType.Password -> requestPassword(false)
HotWalletId.AuthType.Biometry -> HotAuth.Biometry
}
return runCatchingSdkErrors(hotWalletId, auth) {
tangemHotSdk.signHashes(
unlockHotWallet = UnlockHotWallet(
walletId = hotWalletId,
auth = it,
),
dataToSign = dataToSign,
).also {
hotWalletPasswordRequester.dismiss()
}
}
}
private suspend fun <T> runCatchingSdkErrors(
hotWalletId: HotWalletId,
auth: HotAuth,
block: suspend (auth: HotAuth) -> T,
): T {
return runCatchingWrongPassInternal(
originalAuth = auth,
auth = auth,
block = { blockAuth ->
block(blockAuth).also {
// TODO [REDACTED_TASK_KEY] if user has biometry enabled, we set it as the new auth method
if (blockAuth is HotAuth.Password /*&& has biometry enabled */) {
tangemHotSdk.changeAuth(
unlockHotWallet = UnlockHotWallet(
walletId = hotWalletId,
auth = blockAuth,
),
auth = HotAuth.Biometry,
)
}
}
},
)
}
private suspend fun <T> runCatchingWrongPassInternal(
originalAuth: HotAuth,
auth: HotAuth,
block: suspend (auth: HotAuth) -> T,
): T = runCatching {
block(auth)
}.getOrElse { exception ->
if (auth is HotAuth.Biometry && exception.isBiometryError()) {
// fallback to password if biometry fails
val passAuth = requestPassword(true)
return@getOrElse runCatchingWrongPassInternal(
originalAuth = originalAuth,
auth = passAuth,
block = block,
)
}
if (exception !is WrongPasswordException) {
throw exception
}
// If the exception is a wrong password, we need to request the password again
hotWalletPasswordRequester.wrongPassword()
val passResult = requestPassword(originalAuth is HotAuth.Biometry)
runCatchingWrongPassInternal(
originalAuth = originalAuth,
auth = passResult,
block = block,
)
}
private suspend fun requestPassword(hasBiometry: Boolean): HotAuth {
return hotWalletPasswordRequester.requestPassword(hasBiometry).toAuth() ?: throw TangemSdkError.UserCancelled()
}
private fun Throwable.isBiometryError(): Boolean {
return this is TangemSdkError.AuthenticationFailed ||
this is TangemSdkError.AuthenticationCanceled ||
this is TangemSdkError.AuthenticationLockout ||
this is TangemSdkError.AuthenticationUnavailable ||
this is TangemSdkError.AuthenticationAlreadyInProgress ||
this is TangemSdkError.AuthenticationNotInitialized ||
this is TangemSdkError.AuthenticationPermanentLockout
}
private fun HotWalletPasswordRequester.Result.toAuth() = when (this) {
HotWalletPasswordRequester.Result.UseBiometry -> HotAuth.Biometry
HotWalletPasswordRequester.Result.Dismiss -> null
is HotWalletPasswordRequester.Result.EnteredPassword -> this.password
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.domain.hot
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
interface HotWalletPasswordRequester {
suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password
}

View file

@ -0,0 +1,78 @@
package com.tangem.tap.domain.hot
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.map
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.hot.sdk.model.DataToSign
import com.tangem.operations.sign.SignData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
class TangemHotSigner @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Hot,
private val hotWalletAccessor: HotWalletAccessor,
) : TransactionSigner {
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
return sign(listOf(hash), publicKey).map { it.first() }
}
override suspend fun sign(
hashes: List<ByteArray>,
publicKey: Wallet.PublicKey,
): CompletionResult<List<ByteArray>> {
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == publicKey.seedKey }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
val result = hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = listOf(
DataToSign(
curve = wallet.curve,
hashes = hashes,
derivationPath = publicKey.derivationPath,
),
),
)
return CompletionResult.Success(result.map { it.signatures }.flatten())
}
override suspend fun multiSign(
dataToSign: List<SignData>,
publicKey: Wallet.PublicKey,
): CompletionResult<Map<ByteArray, ByteArray>> {
val result = hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = dataToSign.map { signData ->
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == signData.publicKey }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
DataToSign(
curve = wallet.curve,
hashes = listOf(signData.hash),
derivationPath = signData.derivationPath,
)
},
)
return CompletionResult.Success(
result.mapIndexed { index, data ->
dataToSign[index].publicKey to data.signatures.first()
}.toMap(),
)
}
@AssistedFactory
interface Factory {
fun create(@Assisted userWallet: UserWallet.Hot): TangemHotSigner
}
}

View file

@ -0,0 +1,98 @@
package com.tangem.tap.domain.hot
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.map
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.hot.sdk.model.DataToSign
import com.tangem.operations.sign.SignData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
class TangemHotWalletSigner @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Hot,
private val hotWalletAccessor: HotWalletAccessor,
) : TransactionSigner {
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
return sign(listOf(hash), publicKey).map { it.first() }
}
override suspend fun sign(
hashes: List<ByteArray>,
publicKey: Wallet.PublicKey,
): CompletionResult<List<ByteArray>> {
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(publicKey.seedKey) }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
val result = runCatching {
hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = listOf(
DataToSign(
curve = wallet.curve,
hashes = hashes,
derivationPath = publicKey.derivationPath,
),
),
)
}.getOrElse {
Timber.e(it)
return if (it is TangemSdkError) {
CompletionResult.Failure(it)
} else {
CompletionResult.Failure(TangemSdkError.ExceptionError(it))
}
}
return CompletionResult.Success(result.map { it.signatures }.flatten())
}
override suspend fun multiSign(
dataToSign: List<SignData>,
publicKey: Wallet.PublicKey,
): CompletionResult<Map<ByteArray, ByteArray>> {
val result = runCatching {
hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = dataToSign.map { signData ->
val wallet =
userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(signData.publicKey) }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
DataToSign(
curve = wallet.curve,
hashes = listOf(signData.hash),
derivationPath = signData.derivationPath,
)
},
)
}.getOrElse {
Timber.e(it)
return if (it is TangemSdkError) {
CompletionResult.Failure(it)
} else {
CompletionResult.Failure(TangemSdkError.ExceptionError(it))
}
}
return CompletionResult.Success(
result.mapIndexed { index, data ->
dataToSign[index].publicKey to data.signatures.first()
}.toMap(),
)
}
@AssistedFactory
interface Factory {
fun create(@Assisted userWallet: UserWallet.Hot): TangemHotWalletSigner
}
}

View file

@ -23,10 +23,10 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.model.VisaActivationInput
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DerivationTaskResponse

View file

@ -17,10 +17,10 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.model.VisaActivationInput
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
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

View file

@ -177,6 +177,13 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),

View file

@ -4,8 +4,8 @@ import com.tangem.common.card.Card
import com.tangem.common.core.SessionEnvironment
import com.tangem.common.core.TangemSdkError
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.preflightread.PreflightReadFilter
/**

View file

@ -9,8 +9,8 @@ import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext

View file

@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter

View file

@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : TokensFeatureToggles {
override val isStakingLoadingRefactoringEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED")
override val isWalletBalanceFetcherEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED")
}

View file

@ -1,13 +1,12 @@
package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.*
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isLocked
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
@ -213,8 +212,7 @@ internal class BiometricUserWalletsListManager(
changeSelectedUserWallet: Boolean,
canOverridePublicInfo: Boolean,
): CompletionResult<Unit> {
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
val encryptionKey = userWallet.scanResponse.card.encryptionKey
val encryptionKey = userWallet.encryptionKey
?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
?: return CompletionResult.Success(Unit) // No encryption key, no need to save

View file

@ -4,9 +4,9 @@ import com.tangem.common.CompletionResult
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi

View file

@ -2,10 +2,10 @@ package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.model
import com.squareup.moshi.JsonClass
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
@JsonClass(generateAdapter = true)
internal data class UserWalletEncryptionKey(

View file

@ -2,31 +2,44 @@ package com.tangem.tap.domain.userWalletList.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.domain.models.MobileWallet
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.hot.sdk.model.HotWalletId
@JsonClass(generateAdapter = true)
internal data class UserWalletSensitiveInformation(
// Cold
@Json(name = "wallets")
val wallets: List<CardDTO.Wallet>,
val wallets: List<CardDTO.Wallet>?,
@Json(name = "visaCardActivationStatus")
val visaCardActivationStatus: VisaCardActivationStatus? = null,
// Hot
@Json(name = "mobileWallets")
val mobileWallets: List<MobileWallet>? = null,
)
@JsonClass(generateAdapter = true)
internal data class UserWalletPublicInformation(
// Common
@Json(name = "name")
val name: String,
@Json(name = "walletId")
val walletId: UserWalletId,
// Cold
@Json(name = "cardsInWallet")
val cardsInWallet: Set<String>,
@Json(name = "scanResponse")
val scanResponse: ScanResponse,
val scanResponse: ScanResponse?,
@Json(name = "isMultiCurrency")
val isMultiCurrency: Boolean,
@Json(name = "hasBackupError")
val hasBackupError: Boolean = false,
// Hot
@Json(name = "hotWalletId")
val hotWalletId: HotWalletId? = null,
@Json(name = "backedUp")
val backedUp: Boolean? = null,
)

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
internal interface SelectedUserWalletRepository {
suspend fun get(): UserWalletId?

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
internal interface UserWalletsKeysRepository {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
internal interface UserWalletsPublicInformationRepository {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation

View file

@ -10,8 +10,8 @@ import com.tangem.common.services.secure.SecureStorage
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
import kotlinx.coroutines.Dispatchers

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.repository.implementation
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext

View file

@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.common.flatMap
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.utils.publicInformation

View file

@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult
import com.tangem.common.catching
import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.operations.AESCipherOperations
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation

View file

@ -1,7 +1,8 @@
package com.tangem.tap.domain.userWalletList.utils
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation
@ -10,8 +11,12 @@ internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation
is UserWallet.Cold -> UserWalletSensitiveInformation(
wallets = scanResponse.card.wallets,
visaCardActivationStatus = scanResponse.visaCardActivationStatus,
mobileWallets = null,
)
is UserWallet.Hot -> UserWalletSensitiveInformation(
wallets = null,
mobileWallets = this.wallets,
)
is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]")
}
internal val UserWallet.publicInformation: UserWalletPublicInformation
@ -28,19 +33,39 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
visaCardActivationStatus = null,
),
hasBackupError = hasBackupError,
hotWalletId = null,
backedUp = null,
)
is UserWallet.Hot -> UserWalletPublicInformation(
name = name,
walletId = walletId,
isMultiCurrency = isMultiCurrency,
cardsInWallet = emptySet(),
scanResponse = null,
hotWalletId = hotWalletId,
backedUp = backedUp,
)
is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]")
}
internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
return UserWallet.Cold(
name = name,
walletId = walletId,
cardsInWallet = cardsInWallet,
scanResponse = scanResponse,
isMultiCurrency = isMultiCurrency,
hasBackupError = hasBackupError,
)
return if (hotWalletId != null) {
UserWallet.Hot(
name = name,
walletId = walletId,
hotWalletId = hotWalletId,
wallets = null,
backedUp = backedUp!!,
)
} else {
UserWallet.Cold(
name = name,
walletId = walletId,
cardsInWallet = cardsInWallet,
scanResponse = scanResponse!!,
isMultiCurrency = isMultiCurrency,
hasBackupError = hasBackupError,
)
}
}
internal fun List<UserWalletPublicInformation>.toUserWallets(): List<UserWallet> {
@ -53,13 +78,15 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo
copy(
scanResponse = scanResponse.copy(
card = scanResponse.card.copy(
wallets = sensitiveInformation.wallets,
wallets = sensitiveInformation.wallets!!,
),
visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus,
),
)
}
is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]")
is UserWallet.Hot -> copy(
wallets = sensitiveInformation.mobileWallets,
)
}
}
@ -90,5 +117,5 @@ internal fun UserWallet.lock(): UserWallet = when (this) {
),
)
}
is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]")
is UserWallet.Hot -> copy(wallets = null)
}

View file

@ -1,11 +1,16 @@
package com.tangem.tap.domain.userWalletList.utils
import com.tangem.common.extensions.calculateSha256
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.extensions.calculateHmacSha256
import com.tangem.domain.models.MobileWallet
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.wallet.UserWallet
internal val CardDTO.encryptionKey: ByteArray?
get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) }
internal val UserWallet.encryptionKey: ByteArray?
get() = when (this) {
is UserWallet.Cold -> findPublicKey(this.scanResponse.card.wallets)
is UserWallet.Hot -> findPublicKey(this.wallets.orEmpty())
}?.let { calculateEncryptionKey(it) }
private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray()
@ -15,8 +20,12 @@ private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
}
private fun findPublicKey(wallets: List<CardDTO.Wallet>): ByteArray? {
return wallets.firstOrNull()
?.publicKey
return wallets.firstOrNull()?.publicKey
}
@JvmName("findPublicKeyInMobileWallets")
private fun findPublicKey(wallets: List<MobileWallet>): ByteArray? {
return wallets.firstOrNull()?.publicKey
}
private const val MESSAGE_FOR_ENCRYPTION_KEY = "UserWalletEncryptionKey"

View file

@ -17,7 +17,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate

View file

@ -8,12 +8,12 @@ import com.reown.android.relay.ConnectionType
import com.reown.walletkit.client.Wallet
import com.reown.walletkit.client.WalletKit
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.walletconnect.pair.unsupportedDApps
import com.tangem.data.walletconnect.pair.UnsupportedDApps
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.legacy.Account
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
@ -216,7 +216,7 @@ internal class DefaultLegacyWalletConnectRepository(
Timber.i("sessionProposal: $sessionProposal")
this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal
if (sessionProposal.name in unsupportedDApps) {
if (sessionProposal.name in UnsupportedDApps.list) {
Timber.i("Unsupported DApp")
scope.launch {
_events.emit(

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletconnect.model.legacy.Account
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.walletconnect2.domain.models.*
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
import kotlinx.coroutines.flow.Flow

View file

@ -5,16 +5,15 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletconnect.model.legacy.Account
import com.tangem.domain.walletconnect.model.legacy.Session
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.tap.common.extensions.dispatchOnMain
@ -422,8 +421,7 @@ class WalletConnectInteractor(
}
private fun getCardId(userWallet: UserWallet): String? {
userWallet.requireColdWallet() // [REDACTED_TASK_KEY]
return if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
return if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId
} else { // if wallet has backup, any card from wallet can be used to sign
null

View file

@ -43,9 +43,6 @@ object DemoHelper {
}
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
val state = appState() ?: return null
return state.globalState.onboardingState.onboardingManager?.scanResponse
?: state.globalState.scanResponse
return appState()?.globalState?.scanResponse
}
}

View file

@ -9,7 +9,7 @@ import org.rekotlin.Action
sealed class DetailsAction : Action {
data class PrepareScreen(
val scanResponse: ScanResponse,
val scanResponse: ScanResponse?,
val initializedAppSettingsState: AppSettingsState,
) : DetailsAction()

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
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

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.cardsettings.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
interface CardSettingsComponent : ComposableContentComponent {

View file

@ -17,9 +17,9 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.getBackupCardsCount
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -27,7 +27,6 @@ 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.features.details.ui.cardsettings.CardInfo
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
@ -171,6 +170,8 @@ internal class CardSettingsModel @Inject constructor(
}
}
private fun CardDTO.signedHashesCount(): Int = wallets.sumOf { it.totalSignedHashes ?: 0 }
private fun handleClickingItem(item: CardInfo) {
when (item) {
is CardInfo.ChangeAccessCode -> {

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.resetcard.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
interface ResetCardComponent : ComposableContentComponent {

View file

@ -12,9 +12,9 @@ import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.securitymode.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
interface SecurityModeComponent : ComposableContentComponent {

View file

@ -5,6 +5,7 @@ import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.qrscanning.models.QrResultSource
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
@ -31,12 +32,18 @@ internal class WalletConnectModel @Inject constructor(
init {
modelScope.launch {
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
listenToQrScanningUseCase.listen(SourceType.WALLET_CONNECT)
.getOrElse { emptyFlow() }
.map {
.map { result ->
val source = when (result.resultSource) {
QrResultSource.CLIPBOARD -> WalletConnectAction.OpenSession.SourceType.CLIPBOARD
QrResultSource.CAMERA,
QrResultSource.GALLERY,
-> WalletConnectAction.OpenSession.SourceType.QR
}
WalletConnectAction.OpenSession(
wcUri = it,
source = WalletConnectAction.OpenSession.SourceType.QR,
wcUri = result.qrCode,
source = source,
userWalletId = params.userWalletId,
)
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
interface WalletConnectComponent : ComposableContentComponent {
data class Params(val userWalletId: UserWalletId)

View file

@ -13,6 +13,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
import com.tangem.core.ui.utils.findActivity
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.api.HomeComponent
import com.tangem.tap.features.home.compose.StoriesScreen
@ -29,6 +30,7 @@ import org.rekotlin.StoreSubscriber
internal class DefaultHomeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
private val model: HomeModel = getOrCreateModel()
@ -58,7 +60,7 @@ internal class DefaultHomeComponent @AssistedInject constructor(
val activity = LocalContext.current.findActivity()
BackHandler(onBack = activity::finish)
SystemBarsIconsDisposable(darkIcons = false)
if (homeState.value.isV2StoriesEnabled) {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
StoriesScreenV2(
homeState = homeState,
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,

View file

@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtons
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
@ -61,7 +61,7 @@ internal fun StoriesScreen(
StoriesScreenContent(
modifier = Modifier
.fillMaxSize()
.testTag(TestTags.STORIES_SCREEN),
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
config = StoriesScreenContentConfig(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
@ -57,7 +57,7 @@ internal fun StoriesScreenV2(
StoriesScreenContentV2(
modifier = Modifier
.fillMaxSize()
.testTag(TestTags.STORIES_SCREEN),
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
config = StoriesScreenContentV2Config(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,

View file

@ -17,7 +17,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.wallet.R
@Composable
@ -34,7 +34,7 @@ internal fun HomeButtons(
ScanCardButton(
modifier = Modifier
.weight(weight = 1f)
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
showProgress = btnScanStateInProgress,
onClick = onScanButtonClick,
)
@ -42,7 +42,7 @@ internal fun HomeButtons(
OrderCardButton(
modifier = Modifier
.weight(weight = 1f)
.testTag(TestTags.STORIES_SCREEN_ORDER_BUTTON),
.testTag(StoriesScreenTestTags.ORDER_BUTTON),
onClick = onShopButtonClick,
)
}

View file

@ -18,7 +18,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.wallet.R
@Composable
@ -37,19 +37,19 @@ internal fun HomeButtonsV2(
CreateNewWalletButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON),
.testTag(StoriesScreenTestTags.CREATE_NEW_WALLET_BUTTON),
onClick = onCreateNewWalletButtonClick,
)
AddExistingWalletButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON),
.testTag(StoriesScreenTestTags.ADD_EXISTING_WALLET_BUTTON),
onClick = onAddExistingWalletButtonClick,
)
ScanCardButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
showProgress = btnScanStateInProgress,
onClick = onScanButtonClick,
)

View file

@ -61,7 +61,6 @@ private fun handleHomeAction(action: Action) {
Analytics.send(IntroductionProcess.ScreenOpened())
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
}
is HomeAction.ReadCard -> {
action.scope.launch {

View file

@ -7,7 +7,6 @@ import org.rekotlin.StateType
// todo refactor [REDACTED_TASK_KEY]
data class HomeState(
val scanInProgress: Boolean = false,
val isV2StoriesEnabled: Boolean = false,
val stories: ImmutableList<Stories> = getRestrictedStories().toImmutableList(),
) : StateType {

View file

@ -0,0 +1,13 @@
package com.tangem.tap.features.hot
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.tap.domain.hot.HotWalletPasswordRequester
import javax.inject.Inject
class DefaultHotWalletPasswordRequester @Inject constructor() : HotWalletPasswordRequester {
override suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password {
return HotAuth.Password("TODO [REDACTED_TASK_KEY]".toCharArray()) // TODO [REDACTED_TASK_KEY]
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.tap.features.hot
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.model.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import javax.inject.Inject
import javax.inject.Singleton
/**
* Proxy for [TangemHotSdk] to allow lazy initialization and provide a way to access the SDK state from domain layer.
* SDK is initialized in the [com.tangem.tap.routing.component.RoutingComponent] and can be accessed through this proxy.
* Be aware that the SDK is initialized on activity creation, so it may not be available immediately.
*/
@Singleton
class TangemHotSDKProxy @Inject constructor() : TangemHotSdk {
val sdkState = MutableStateFlow<TangemHotSdk?>(null)
override suspend fun importWallet(mnemonic: Mnemonic, passphrase: CharArray?, auth: HotAuth): HotWalletId =
callSdk { importWallet(mnemonic, passphrase, auth) }
override suspend fun generateWallet(auth: HotAuth, mnemonicType: MnemonicType): HotWalletId =
callSdk { generateWallet(auth, mnemonicType) }
override suspend fun exportMnemonic(unlockHotWallet: UnlockHotWallet): SeedPhrasePrivateInfo =
callSdk { exportMnemonic(unlockHotWallet) }
override suspend fun exportBackup(unlockHotWallet: UnlockHotWallet): ByteArray =
callSdk { exportBackup(unlockHotWallet) }
override suspend fun delete(id: HotWalletId) = callSdk { delete(id) }
override suspend fun changeAuth(unlockHotWallet: UnlockHotWallet, auth: HotAuth): HotWalletId =
callSdk { changeAuth(unlockHotWallet, auth) }
override suspend fun derivePublicKey(
unlockHotWallet: UnlockHotWallet,
request: DeriveWalletRequest,
): DerivedPublicKeyResponse = callSdk { derivePublicKey(unlockHotWallet, request) }
override suspend fun signHashes(unlockHotWallet: UnlockHotWallet, dataToSign: List<DataToSign>): List<SignedData> =
callSdk { signHashes(unlockHotWallet, dataToSign) }
private suspend fun <T> callSdk(block: suspend TangemHotSdk.() -> T): T {
return withTimeout(timeMillis = 1000) {
sdkState.filterNotNull().first()
}.block()
}
}

View file

@ -4,13 +4,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.keyboard.KeyboardValidator
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.ui.R
import com.tangem.core.ui.coil.ImagePreloader
import com.tangem.core.ui.extensions.resourceReference
@ -18,11 +16,16 @@ import com.tangem.core.ui.message.BottomSheetMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.common.LogConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
import com.tangem.domain.notifications.models.ApplicationId
@ -37,15 +40,16 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase
import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
import com.tangem.feature.swap.analytics.StoriesEvents
import com.tangem.features.onramp.deeplink.OnrampDeepLink
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.wallet.BuildConfig
@ -58,7 +62,7 @@ import timber.log.Timber
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
@HiltViewModel
internal class MainViewModel @Inject constructor(
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
@ -78,8 +82,6 @@ internal class MainViewModel @Inject constructor(
private val imagePreloader: ImagePreloader,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val onboardingRepository: OnboardingRepository,
private val deepLinksRegistry: DeepLinksRegistry,
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
private val notificationsToggles: NotificationsFeatureToggles,
private val getApplicationIdUseCase: GetApplicationIdUseCase,
private val subscribeOnWalletsUseCase: GetSavedWalletsCountUseCase,
@ -88,7 +90,8 @@ internal class MainViewModel @Inject constructor(
private val sendPushTokenUseCase: SendPushTokenUseCase,
private val apiConfigsManager: ApiConfigsManager,
private val multiQuoteUpdater: MultiQuoteUpdater,
routingFeatureToggle: RoutingFeatureToggle,
private val appStateHolder: AppStateHolder,
private val environmentConfigStorage: EnvironmentConfigStorage,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel() {
@ -120,6 +123,8 @@ internal class MainViewModel @Inject constructor(
multiQuoteUpdater.subscribe()
initializeOffRamp()
observeFlips()
displayBalancesHidingStatusToast()
displayHiddenBalancesModalNotification()
@ -129,10 +134,6 @@ internal class MainViewModel @Inject constructor(
sendKeyboardIdentifierEvent()
preloadImages()
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
initializeDeepLinks()
}
}
override fun onCleared() {
@ -189,7 +190,7 @@ internal class MainViewModel @Inject constructor(
userWalletsListManager.selectedUserWallet
.distinctUntilChanged()
.onEach { userWallet ->
Analytics.setContext(userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY]
Analytics.setContext(userWallet)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
@ -201,6 +202,28 @@ internal class MainViewModel @Inject constructor(
.onRight { Timber.d("Staking token list was fetched successfully") }
}
private fun initializeOffRamp() {
viewModelScope.launch {
val sellService = makeSellExchangeService(environmentConfig = environmentConfigStorage.getConfigSync())
appStateHolder.sellService = sellService
sellService.update()
}
}
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
val cardProvider: () -> ScanResponse? = {
userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY]
}
return MoonPayService(
apiKey = environmentConfig.moonPayApiKey,
secretKey = environmentConfig.moonPayApiSecretKey,
logEnabled = LogConfig.network.moonPayService,
cardProvider = { cardProvider.invoke()?.card },
)
}
private fun observeFlips() {
listenToFlipsUseCase().launchIn(viewModelScope)
}
@ -384,7 +407,7 @@ internal class MainViewModel @Inject constructor(
}
}
} catch (ex: Exception) {
Timber.e(ex.message)
Timber.e(ex)
analyticsEventHandler.send(
StoriesEvents.Error(
type = StoryContentIds.STORY_FIRST_TIME_SWAP.analyticType,
@ -393,10 +416,6 @@ internal class MainViewModel @Inject constructor(
}
}
private fun initializeDeepLinks() {
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
}
private suspend fun initPushNotifications() {
if (notificationsToggles.isNotificationsEnabled) {
getApplicationIdUseCase().onRight { applicationId ->

View file

@ -1,6 +0,0 @@
package com.tangem.tap.features.onboarding
import com.tangem.domain.models.scan.ScanResponse
@Deprecated("Remove when navigation refactoring will be implemented")
class OnboardingManager(var scanResponse: ScanResponse)

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.models.wallet.UserWalletId
import org.rekotlin.Action
sealed class OnboardingWalletAction : Action {

View file

@ -27,7 +27,7 @@ object WalletActivationErrorDialog {
// changed on email support [REDACTED_TASK_KEY]
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
val scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
val scanResponse = store.state.globalState.scanResponse
?: error("ScanResponse must be not null")
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()

View file

@ -1,30 +1,18 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@Deprecated("Will be removed soon")
@ -46,79 +34,10 @@ object TradeCryptoMiddleware {
when (action) {
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
is TradeCryptoAction.Buy -> proceedBuyAction(action)
is TradeCryptoAction.Sell -> proceedSellAction(action)
}
}
private fun proceedBuyAction(action: TradeCryptoAction.Buy) {
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
if (isOnrampEnabled) {
proceedWithOnramp(action.userWallet.walletId, action.cryptoCurrencyStatus.currency, action.source)
} else {
proceedWithLegacyBuyAction(action)
}
}
private fun proceedWithOnramp(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, source: OnrampSource) {
store.dispatchNavigationAction {
push(
AppRoute.Onramp(
userWalletId = userWalletId,
currency = cryptoCurrency,
source = source,
),
)
}
}
private fun proceedWithLegacyBuyAction(action: TradeCryptoAction.Buy) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
?.let(NetworkAddress.Address::value)
?: return
val status = action.cryptoCurrencyStatus
val currency = status.currency
val blockchain = currency.network.toBlockchain()
val exchangeManager = store.state.globalState.exchangeManager
val topUrl = exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
cryptoCurrency = currency,
fiatCurrencyName = action.appCurrencyCode,
walletAddress = networkAddress,
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
)
scope.launch {
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
val walletManager = store.inject(DaggerGraphState::walletManagersFacade)
.getOrCreateWalletManager(
userWalletId = action.userWallet.walletId,
blockchain = blockchain,
derivationPath = currency.network.derivationPath.value,
)
if (walletManager !is EthereumWalletManager) {
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
return@launch
}
buyErc20TestnetTokens(
card = action.userWallet.requireColdWallet().scanResponse.card, // TODO [REDACTED_TASK_KEY]
walletManager = walletManager,
destinationAddress = currency.contractAddress,
)
return@launch
}
topUrl?.let {
store.dispatchOpenUrl(it)
Analytics.send(Token.Topup.ScreenOpened())
}
}
}
private fun proceedSellAction(action: TradeCryptoAction.Sell) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
@ -126,8 +45,7 @@ object TradeCryptoMiddleware {
?: return
val currency = action.cryptoCurrencyStatus.currency
store.state.globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Sell,
store.inject(DaggerGraphState::appStateHolder).sellService?.getUrl(
cryptoCurrency = currency,
fiatCurrencyName = action.appCurrencyCode,
walletAddress = networkAddress,
@ -140,9 +58,9 @@ object TradeCryptoMiddleware {
private fun openReceiptUrl(transactionId: String) {
store.dispatchNavigationAction(AppRouter::pop)
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
action = CurrencyExchangeManager.Action.Sell,
transactionId = transactionId,
)?.let { store.dispatchOpenUrl(it) }
val sellService = store.inject(DaggerGraphState::appStateHolder).sellService
sellService?.getSellCryptoReceiptUrl(transactionId = transactionId)
?.let(store::dispatchOpenUrl)
}
}

View file

@ -131,6 +131,5 @@ internal class WelcomeModel @Inject constructor(
private fun initGlobalState() {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
}
}

View file

@ -13,20 +13,16 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
import com.tangem.domain.wallets.legacy.unlockIfLockable
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.backupService
import com.tangem.tap.*
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@ -95,7 +91,7 @@ internal class WelcomeMiddleware {
}
.doOnSuccess { selectedUserWallet ->
sendSignedInAnalyticsEvent(
scanResponse = selectedUserWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
userWallet = selectedUserWallet,
signInType = Basic.SignedIn.SignInType.Biometric,
)
@ -129,7 +125,7 @@ internal class WelcomeMiddleware {
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
}
.doOnSuccess {
sendSignedInAnalyticsEvent(scanResponse = scanResponse, signInType = Basic.SignedIn.SignInType.Card)
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedIn.SignInType.Card)
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
@ -142,7 +138,14 @@ internal class WelcomeMiddleware {
}
}
private fun sendSignedInAnalyticsEvent(scanResponse: ScanResponse, signInType: Basic.SignedIn.SignInType) {
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedIn.SignInType) {
// TODO [REDACTED_TASK_KEY]
if (userWallet !is UserWallet.Cold) {
return
}
val scanResponse = userWallet.scanResponse
val currency = ParamCardCurrencyConverter().convert(
value = scanResponse.cardTypesResolver,
)

View file

@ -2,20 +2,29 @@ package com.tangem.tap.network.auth
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
internal class DefaultAuthProvider(private val userWalletsListManager: UserWalletsListManager) : AuthProvider {
override fun getCardPublicKey(): String {
return userWalletsListManager.selectedUserWalletSync
?.requireColdWallet()?.scanResponse?.card?.cardPublicKey?.toHexString() ?: ""
val userWallet = userWalletsListManager.selectedUserWalletSync
if (userWallet !is UserWallet.Cold) {
return ""
}
return userWallet.scanResponse.card.cardPublicKey.toHexString()
}
override fun getCardId(): String {
return userWalletsListManager.selectedUserWalletSync
?.requireColdWallet()?.scanResponse?.card?.cardId ?: ""
val userWallet = userWalletsListManager.selectedUserWalletSync
if (userWallet !is UserWallet.Cold) {
return ""
}
return userWallet.scanResponse.card.cardId
}
override fun getCardsPublicKeys(): Map<String, String> {

View file

@ -1,31 +0,0 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.demo.isDemoCard
/**
[REDACTED_AUTHOR]
*/
class CardExchangeRules(
val cardProvider: () -> CardDTO?,
) : ExchangeRules {
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
val card = scanResponse.card
return when {
card.isDemoCard() -> true
card.isStart2Coin -> false
else -> true
}
}
override fun availableForSell(currency: Currency): Boolean {
val card = cardProvider() ?: return false
return !card.isStart2Coin
}
}

View file

@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -25,10 +24,8 @@ internal class CryptoCurrencyConverter(
cryptoCurrencyFactory.createCoin(
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
scanResponse = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync
?.requireColdWallet() // TODO [REDACTED_TASK_KEY]
?.scanResponse,
userWallet = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync,
),
),
)
@ -37,10 +34,8 @@ internal class CryptoCurrencyConverter(
sdkToken = value.token,
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
scanResponse = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync
?.requireColdWallet() // TODO [REDACTED_TASK_KEY]
?.scanResponse,
userWallet = requireNotNull(
store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync,
),
),
)

View file

@ -1,148 +0,0 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.Message
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
class CurrencyExchangeManager(
private val buyService: ExchangeService,
private val sellService: ExchangeService,
private val primaryRules: ExchangeRules,
) : ExchangeService {
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
get() = _initializationStatus
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
MutableStateFlow(value = lceLoading())
override suspend fun update() {
_initializationStatus.value = lceLoading()
if (!store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled) {
buyService.update()
}
sellService.update()
_initializationStatus.value = lceContent()
}
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
return primaryRules.availableForBuy(scanResponse, currency) &&
buyService.availableForBuy(scanResponse, currency)
}
override fun availableForSell(currency: Currency): Boolean {
return primaryRules.availableForSell(currency) && sellService.availableForSell(currency)
}
override fun getUrl(
action: Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String? {
val blockchain = cryptoCurrency.network.toBlockchain()
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
val urlBuilder = getExchangeUrlBuilder(action)
return urlBuilder.getUrl(
action,
cryptoCurrency,
fiatCurrencyName,
walletAddress,
isDarkTheme,
)
}
override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? {
val urlBuilder = getExchangeUrlBuilder(action)
return urlBuilder.getSellCryptoReceiptUrl(action, transactionId)
}
private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder {
return when (action) {
Action.Buy -> buyService
Action.Sell -> sellService
}
}
enum class Action { Buy, Sell }
companion object {
fun dummy(): CurrencyExchangeManager = CurrencyExchangeManager(
buyService = ExchangeService.dummy(),
sellService = ExchangeService.dummy(),
primaryRules = ExchangeRules.dummy(),
)
}
}
@Deprecated("Useless")
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) {
walletManager.safeUpdate(card.isDemoCard())
val amountToSend = Amount(walletManager.wallet.blockchain)
val feeResult = walletManager.getFee(amountToSend, destinationAddress) as? Result.Success ?: return
val fee = when (val feeForTx = feeResult.data) {
is TransactionFee.Choosable -> feeForTx.minimum
is TransactionFee.Single -> feeForTx.normal
}
val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
if (coinValue < fee.amount.value) return
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
val signer = TangemSigner(
cardId = card.cardId.takeIf { isCardNotBackedUp },
tangemSdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk,
initialMessage = Message(),
twinKey = null,
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures,
),
)
store.dispatch(action = GlobalAction.IsSignWithRing(signResponse.isRing))
}
walletManager.send(
transactionData = walletManager.createTransaction(
amount = amountToSend,
fee = fee,
destination = destinationAddress,
),
signer = signer,
)
}

View file

@ -13,12 +13,11 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.exchange.ExpressAvailabilityState
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
@ -28,26 +27,22 @@ import kotlinx.coroutines.flow.firstOrNull
@Suppress("LongParameterList")
internal class DefaultRampManager(
private val exchangeService: ExchangeService?,
private val buyService: Provider<ExchangeService>,
private val sellService: Provider<ExchangeService>,
private val expressServiceLoader: ExpressServiceLoader,
private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val onrampFeatureToggles: OnrampFeatureToggles,
excludedBlockchains: ExcludedBlockchains,
) : RampStateManager {
private val cryptoCurrencyConverter = CryptoCurrencyConverter(excludedBlockchains)
override suspend fun availableForBuy(
scanResponse: ScanResponse,
userWalletId: UserWalletId,
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
): ScenarioUnavailabilityReason {
val availabilityState = runCatching {
getOnrampAvailableState(scanResponse, userWalletId, cryptoCurrency)
}.getOrNull() ?: ExpressAvailabilityState.Error
val availabilityState = runCatching { getOnrampAvailableState(userWallet.walletId, cryptoCurrency) }
.getOrNull()
?: ExpressAvailabilityState.Error
return availabilityState.toReason(cryptoCurrency.name)
}
@ -62,7 +57,7 @@ internal class DefaultRampManager(
block = {
val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency)
exchangeService?.availableForSell(currency = serviceCurrency) == true
sellService().availableForSell(currency = serviceCurrency)
},
catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) },
)
@ -100,16 +95,6 @@ internal class DefaultRampManager(
return availabilityState.toReason(cryptoCurrency.name)
}
override fun getBuyInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
return buyService.invoke().initializationStatus
}
override suspend fun fetchBuyServiceData() {
runCatching(dispatchers.io) {
buyService.invoke().update()
}
}
override fun getSellInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
return sellService.invoke().initializationStatus
}
@ -163,31 +148,20 @@ internal class DefaultRampManager(
}
private suspend fun getOnrampAvailableState(
scanResponse: ScanResponse,
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExpressAvailabilityState {
return when {
onrampFeatureToggles.isFeatureEnabled -> {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading
return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound
}
}
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading
return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound
}
exchangeService != null -> {
exchangeService.availableForBuy(
scanResponse = scanResponse,
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
).toOnrampAvailabilityState()
}
else -> ExpressAvailabilityState.Error
}
}

View file

@ -1,75 +1,26 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.model.Currency
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
typealias ExchangeServiceInitializationStatus = Lce<Throwable, Any>
interface Exchanger {
fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean
fun availableForSell(currency: Currency): Boolean
}
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
interface ExchangeService {
val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
suspend fun update()
companion object {
fun dummy(): ExchangeService = object : ExchangeService {
fun availableForSell(currency: Currency): Boolean
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus> =
MutableStateFlow(value = lceLoading())
override suspend fun update() {}
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
override fun getUrl(
action: CurrencyExchangeManager.Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String? = null
override fun getSellCryptoReceiptUrl(
action: CurrencyExchangeManager.Action,
transactionId: String,
): String? = null
}
}
}
interface ExchangeRules : Exchanger {
companion object {
fun dummy(): ExchangeRules = object : ExchangeRules {
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
}
}
}
interface ExchangeUrlBuilder {
@Suppress("LongParameterList")
fun getUrl(
action: CurrencyExchangeManager.Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String?
fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
companion object {
const val SCHEME = "https"
const val SUCCESS_URL = "https://tangem.com/success"
}
fun getSellCryptoReceiptUrl(transactionId: String): String?
}

View file

@ -1,42 +0,0 @@
package com.tangem.tap.network.exchangeServices.mercuryo
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import retrofit2.http.GET
import retrofit2.http.Path
interface MercuryoApi {
@GET("{apiVersion}/lib/currencies")
suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse
}
@JsonClass(generateAdapter = true)
data class MercuryoCurrenciesResponse(
@Json(name = "status") val status: Int,
@Json(name = "data") val data: Data,
) {
@JsonClass(generateAdapter = true)
data class Data(
@Json(name = "fiat") val fiat: List<String>,
@Json(name = "crypto") val crypto: List<String>,
@Json(name = "config") val config: Config,
)
@JsonClass(generateAdapter = true)
data class Config(
@Json(name = "crypto_currencies")
val cryptoCurrencies: List<MercuryoCryptoCurrency>,
)
@JsonClass(generateAdapter = true)
data class MercuryoCryptoCurrency(
@Json(name = "currency")
val currencySymbol: String,
@Json(name = "network")
val network: String,
@Json(name = "contract")
val contractAddress: String,
)
}

View file

@ -1,32 +0,0 @@
package com.tangem.tap.network.exchangeServices.mercuryo
import com.tangem.datasource.api.common.createRetrofitInstance
/**
[REDACTED_AUTHOR]
*/
data class MercuryoEnvironment(
val baseUrl: String,
val apiVersion: String,
val widgetId: String,
val secret: String,
val mercuryoApi: MercuryoApi,
) {
companion object {
private const val BASE_URL = "https://api.mercuryo.io/"
private const val API_VERSION = "v1.6"
fun prod(widgetId: String, secret: String, apiVersion: String = API_VERSION): MercuryoEnvironment {
return MercuryoEnvironment(
baseUrl = BASE_URL,
apiVersion = apiVersion,
widgetId = widgetId,
secret = secret,
mercuryoApi = createRetrofitInstance(
baseUrl = BASE_URL,
logEnabled = false,
).create(MercuryoApi::class.java),
)
}
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.tap.network.exchangeServices.mercuryo
import android.net.Uri
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.common.extensions.calculateSha512
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.data.onramp.legacy.mercuryoNetwork
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList
/**
[REDACTED_AUTHOR]
*/
internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService {
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
get() = _initializationStatus
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
MutableStateFlow(value = lceLoading())
private val api: MercuryoApi = environment.mercuryoApi
private val availableMercuryoCurrencies = CopyOnWriteArrayList<MercuryoCurrenciesResponse.MercuryoCryptoCurrency>()
override suspend fun update() {
Timber.i("Start updating")
_initializationStatus.value = lceLoading()
val result = performRequest { api.currencies(environment.apiVersion) }
when {
result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> {
handleSuccessfullyUpdatedData(data = result.data.data)
}
result is Result.Failure -> {
availableMercuryoCurrencies.clear()
Timber.e("Failed to load currencies", result.error)
_initializationStatus.value = result.error.lceError()
}
}
}
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
val mercuryoNetwork = currency.blockchain.mercuryoNetwork
val contractAddress = (currency as? Currency.Token)?.token?.contractAddress ?: ""
val availableCurrency = availableMercuryoCurrencies.firstOrNull {
it.currencySymbol == currency.currencySymbol &&
it.network == mercuryoNetwork &&
it.contractAddress.equals(contractAddress, ignoreCase = true)
}
return availableCurrency != null
}
override fun availableForSell(currency: Currency): Boolean = false
override fun getUrl(
action: CurrencyExchangeManager.Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String {
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
val blockchain = cryptoCurrency.network.toBlockchain()
val builder = Uri.Builder()
.scheme(ExchangeUrlBuilder.SCHEME)
.authority("exchange.mercuryo.io")
.appendQueryParameter("widget_id", environment.widgetId)
.appendQueryParameter("type", action.name.lowercase())
.appendQueryParameter("currency", cryptoCurrency.symbol)
.appendQueryParameter("address", walletAddress)
.appendQueryParameter("signature", signature(walletAddress))
.appendQueryParameter("fix_currency", "true")
.appendQueryParameter("redirect_url", ExchangeUrlBuilder.SUCCESS_URL)
if (isDarkTheme) builder.appendQueryParameter("theme", "1inch")
blockchain.mercuryoNetwork?.let {
builder.appendQueryParameter("network", it)
}
return builder.build().toString()
}
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
availableMercuryoCurrencies.clear()
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
Timber.i("Successfully updated")
_initializationStatus.value = lceContent()
}
private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
private companion object {
const val RESPONSE_SUCCESS_STATUS_CODE = 200
}
}

View file

@ -6,17 +6,16 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withIOContext
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -28,6 +27,7 @@ class MoonPayService(
private val apiKey: String,
private val secretKey: String,
private val logEnabled: Boolean,
private val cardProvider: () -> CardDTO?,
) : ExchangeService {
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
@ -102,9 +102,12 @@ class MoonPayService(
}
}
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean {
val card = cardProvider() ?: return false
val checkCardExchange = !card.isStart2Coin
if (!checkCardExchange) return false
if (!isSellAllowed()) return false
val availableForSell = status?.availableForSell ?: return false
@ -125,15 +128,14 @@ class MoonPayService(
}
override fun getUrl(
action: CurrencyExchangeManager.Action,
cryptoCurrency: CryptoCurrency,
fiatCurrencyName: String,
walletAddress: String,
isDarkTheme: Boolean,
): String? {
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
val blockchain = cryptoCurrency.network.toBlockchain()
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
val moonpayCurrency = status?.availableForSell?.firstOrNull {
when (cryptoCurrency) {
@ -165,7 +167,7 @@ class MoonPayService(
return uri.build().toString()
}
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String {
override fun getSellCryptoReceiptUrl(transactionId: String): String {
return Uri.Builder()
.scheme(SCHEME)
.authority(URL_SELL)
@ -185,8 +187,9 @@ class MoonPayService(
return status?.responseUserStatus?.isSellAllowed ?: false
}
companion object {
private companion object {
const val URL_SELL = "sell.moonpay.com"
const val SCHEME = "https"
}
}

View file

@ -1,8 +1,8 @@
package com.tangem.tap.proxy
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
@ -19,8 +19,6 @@ import javax.inject.Inject
class AppStateHolder @Inject constructor() : ReduxStateHolder {
var mainStore: Store<AppState>? = null
var exchangeService: ExchangeService? = null
var buyService: ExchangeService? = null
var sellService: ExchangeService? = null
override fun dispatch(action: Action) {

View file

@ -32,9 +32,7 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.operations.attestation.CardArtworksProvider
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -68,7 +66,6 @@ data class DaggerGraphState(
val shareManager: ShareManager? = null,
val appRouter: AppRouter? = null,
val transactionSignerFactory: TransactionSignerFactory? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null,
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
val onboardingRepository: OnboardingRepository? = null,
@ -77,7 +74,6 @@ data class DaggerGraphState(
val clipboardManager: ClipboardManager? = null,
val settingsManager: SettingsManager? = null,
val uiMessageSender: UiMessageSender? = null,
val onlineCardVerifier: OnlineCardVerifier? = null,
val cardArworksProvider: CardArtworksProvider? = null,
val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null,
val userTokensResponseStore: UserTokensResponseStore? = null,

View file

@ -75,6 +75,7 @@ internal class ProxyAppRouter(
try {
block()
} catch (e: Throwable) {
Timber.e(e)
onComplete(false)
}
}

View file

@ -34,6 +34,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.transitions.RoutingTransitionAnimationFactory
@Suppress("LongParameterList")
@OptIn(ExperimentalDecomposeApi::class)
@Composable
internal fun RootContent(
@ -41,6 +42,7 @@ internal fun RootContent(
backHandler: BackHandler,
uiDependencies: UiDependencies,
wcContent: @Composable (modifier: Modifier) -> Unit,
hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -74,6 +76,8 @@ internal fun RootContent(
wcContent(Modifier.fillMaxSize())
hotAccessCodeContent(Modifier.fillMaxSize())
TangemSnackbarHost(
modifier = Modifier
.align(Alignment.BottomCenter)

View file

@ -6,17 +6,24 @@ import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.subscribe
import com.arkivanov.essenty.lifecycle.subscribe
import com.google.android.material.snackbar.Snackbar
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.navigation.getOrCreateTyped
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.android.create
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.features.hot.TangemHotSDKProxy
import com.tangem.tap.routing.RootContent
import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.component.RoutingComponent.Child
@ -36,13 +43,21 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
private val uiDependencies: UiDependencies,
private val wcRoutingComponentFactory: WcRoutingComponent.Factory,
private val deeplinkFactory: DeepLinkFactory,
private val tangemHotSDKProxy: TangemHotSDKProxy,
private val hotAccessCodeRequestComponentFactory: HotAccessCodeRequestComponent.Factory,
private val hotAccessCodeRequesterProxy: HotWalletPasswordRequesterProxy,
) : RoutingComponent,
AppComponentContext by context,
SnackbarHandler {
private val wcRoutingComponent: WcRoutingComponent by lazy {
wcRoutingComponentFactory
.create(childByContext(componentContext = this), params = Unit)
.create(child("wcRoutingComponent"), params = Unit)
}
private val hotAccessCodeRequestComponent: HotAccessCodeRequestComponent by lazy {
hotAccessCodeRequestComponentFactory
.create(child("hotAccessCodeRequestComponent"), Unit)
}
private val stack: Value<ChildStack<AppRoute, Child>> = childStack(
@ -70,6 +85,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
appRouterConfig.stack = stackItems
}
}
configureProxies()
}
@Composable
@ -79,6 +96,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
stack = stack,
uiDependencies = uiDependencies,
wcContent = { wcRoutingComponent.Content(it) },
hotAccessCodeContent = { hotAccessCodeRequestComponent.Content(it) },
backHandler = backHandler,
onBack = router::pop,
)
@ -120,6 +138,19 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
initialStack
}
private fun configureProxies() {
lifecycle.subscribe(
onCreate = {
tangemHotSDKProxy.sdkState.value = TangemHotSdk.create(activity)
hotAccessCodeRequesterProxy.componentRequester.value = hotAccessCodeRequestComponent
},
onDestroy = {
tangemHotSDKProxy.sdkState.value = null
hotAccessCodeRequesterProxy.componentRequester.value = null
},
)
}
@AssistedFactory
interface Factory : RoutingComponent.Factory {
override fun create(context: AppComponentContext, initialStack: List<AppRoute>?): DefaultRoutingComponent

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