Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-25 14:22:59 +03:00
commit 98dd90235a
280 changed files with 5494 additions and 1083 deletions

View file

@ -3,7 +3,6 @@ package com.tangem.tap
import androidx.hilt.work.HiltWorkerFactory
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.data.card.TransactionSignerFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.filter.OneTimeEventFilter
@ -15,6 +14,7 @@ import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.data.card.TransactionSignerFactory
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
@ -32,13 +32,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.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
@ -94,8 +94,6 @@ interface ApplicationEntryPoint {
fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase
fun getWalletNameGenerateUseCase(): GenerateWalletNameUseCase
fun getCardRepository(): CardRepository
fun getTangemSdkLogger(): TangemSdkLogger
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
fun getOnrampFeatureToggles(): OnrampFeatureToggles
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
@ -142,4 +138,8 @@ interface ApplicationEntryPoint {
fun getUiMessageSender(): UiMessageSender
fun getWorkerFactory(): HiltWorkerFactory
fun getOnlineCardVerifier(): OnlineCardVerifier
fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory
}

View file

@ -8,6 +8,7 @@ import coil.ImageLoaderFactory
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.tangem.Log
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.ExceptionHandler
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
@ -46,15 +47,16 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.images.createCoilImageLoader
@ -146,9 +148,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
get() = entryPoint.getSaveTwinsOnboardingShownUseCase()
private val generateWalletNameUseCase: GenerateWalletNameUseCase
get() = entryPoint.getWalletNameGenerateUseCase()
private val cardRepository: CardRepository
get() = entryPoint.getCardRepository()
@ -182,9 +181,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val transactionSignerFactory: TransactionSignerFactory
get() = entryPoint.getTransactionSignerFactory()
private val getUserCountryUseCase: GetUserCountryUseCase
get() = entryPoint.getGetUserCountryCodeUseCase()
private val onrampFeatureToggles: OnrampFeatureToggles
get() = entryPoint.getOnrampFeatureToggles()
@ -212,6 +208,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val uiMessageSender: UiMessageSender
get() = entryPoint.getUiMessageSender()
private val blockchainExceptionHandler: BlockchainExceptionHandler
get() = entryPoint.getBlockchainExceptionHandler()
private val workerFactory: HiltWorkerFactory
get() = entryPoint.getWorkerFactory()
@ -220,6 +219,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
.setWorkerFactory(workerFactory)
.build()
private val onlineCardVerifier: OnlineCardVerifier
get() = entryPoint.getOnlineCardVerifier()
private val userWalletBuilderFactory: UserWalletBuilder.Factory
get() = entryPoint.getUserWalletBuilderFactory()
// endregion
override fun onCreate() {
@ -265,7 +270,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
}
loadNativeLibraries()
// ExceptionHandler.append(blockchainExceptionHandler) // TODO [REDACTED_TASK_KEY] Send only to Firebase
ExceptionHandler.append(blockchainExceptionHandler)
if (LogConfig.network.blockchainSdkNetwork) {
BlockchainSdkRetrofitBuilder.interceptors = listOf(
createNetworkLoggingInterceptor(),
@ -301,7 +306,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
generalUserWalletsListManager = generalUserWalletsListManager,
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
generateWalletNameUseCase = generateWalletNameUseCase,
cardRepository = cardRepository,
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
@ -312,7 +316,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
shareManager = shareManager,
appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory,
getUserCountryUseCase = getUserCountryUseCase,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
@ -322,6 +325,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
clipboardManager = clipboardManager,
settingsManager = settingsManager,
uiMessageSender = uiMessageSender,
onlineCardVerifier = onlineCardVerifier,
userWalletBuilderFactory = userWalletBuilderFactory,
),
),
)

View file

@ -1,20 +1,20 @@
package com.tangem.tap.common.analytics
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.core.analytics.api.ErrorEventLogger
import com.tangem.core.analytics.api.ExceptionLogger
import com.tangem.core.analytics.api.EventLogger
import timber.log.Timber
class AnalyticsEventsLogger(
private val name: String,
private val jsonConverter: MoshiJsonConverter,
) : EventLogger, ErrorEventLogger {
) : EventLogger, ExceptionLogger {
override fun logEvent(event: String, params: Map<String, String>) {
Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params)))
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
override fun logException(error: Throwable, params: Map<String, String>) {
Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params)))
}
}

View file

@ -6,7 +6,6 @@ internal sealed class Push(event: String) : AnalyticsEvent(
category = "Push",
event = event,
params = emptyMap(),
error = null,
) {
data object PushNotificationOpened : Push(event = "Push Notification Opened")

View file

@ -9,8 +9,7 @@ sealed class Settings(
category: String = "Settings",
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) {
) : AnalyticsEvent(category, event, params) {
class ScreenOpened : Settings(event = "Settings Screen Opened")
class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
@ -19,16 +18,14 @@ sealed class Settings(
sealed class CardSettings(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : Settings("Settings / Card Settings", event, params, error) {
) : Settings("Settings / Card Settings", event, params) {
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
class FactoryResetFinished(cardsCount: Int? = null, error: Throwable? = null) : CardSettings(
class FactoryResetFinished(cardsCount: Int? = null) : CardSettings(
event = "Factory Reset Finished",
params = buildMap {
cardsCount?.let { put("Cards Count", "$it") }
},
error = error,
)
class FactoryResetCanceled(cardsCount: Int) : CardSettings(
@ -44,10 +41,9 @@ sealed class Settings(
params = mapOf("Type" to type.value),
)
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings(
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
event = "Security Mode Changed",
params = mapOf("Mode" to mode.value),
error = error,
)
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(

View file

@ -8,8 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
sealed class SignIn(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent("Sign In", event, params, error) {
) : AnalyticsEvent("Sign In", event, params) {
class ScreenOpened : SignIn(event = "Sign In Screen Opened")

View file

@ -10,8 +10,7 @@ sealed class Token(
category: String,
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) {
) : AnalyticsEvent(category, event, params) {
sealed class Receive(
event: String,

View file

@ -9,8 +9,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.O
internal sealed class WalletConnect(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent("Wallet Connect", event, params, error) {
) : AnalyticsEvent("Wallet Connect", event, params) {
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
class NewSessionInitiated(source: SourceType) : WalletConnect(

View file

@ -1,15 +1,15 @@
package com.tangem.tap.common.analytics.handlers
import com.tangem.blockchain.common.ExceptionHandlerOutput
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.tap.common.analytics.events.BlockchainExceptionEvent
import javax.inject.Inject
class BlockchainExceptionHandler @Inject constructor(
private val analyticsHandler: AnalyticsEventHandler,
private val analyticsErrorHandler: AnalyticsErrorHandler,
) : ExceptionHandlerOutput {
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
analyticsHandler.send(
analyticsErrorHandler.sendErrorEvent(
BlockchainExceptionEvent(
selectedHost = nextHost,
exceptionHost = currentHost,

View file

@ -1,11 +1,13 @@
package com.tangem.tap.common.analytics.handlers.amplitude
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
class AmplitudeAnalyticsHandler(
private val client: AmplitudeAnalyticsClient,
) : AnalyticsHandler {
) : AnalyticsHandler, AnalyticsErrorHandler {
override fun id(): String = ID
@ -13,6 +15,10 @@ class AmplitudeAnalyticsHandler(
client.logEvent(eventId, params)
}
override fun sendErrorEvent(event: AnalyticsEvent) {
send(event.id, event.params)
}
companion object {
const val ID = "Amplitude"
}

View file

@ -1,16 +1,18 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.ErrorEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
import com.tangem.tap.common.analytics.events.Shop
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
) : AnalyticsHandler, ErrorEventHandler {
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler {
private val errorConverter = AnalyticsErrorConverter()
override fun id(): String = ID
@ -18,30 +20,17 @@ class FirebaseAnalyticsHandler(
client.logEvent(eventId, params)
}
override fun send(event: AnalyticsEvent) {
val error = event.error
when {
error != null -> {
val errorConverter = AnalyticsErrorConverter()
if (!errorConverter.canBeHandled(error)) return
override fun sendException(event: ExceptionAnalyticsEvent) {
if (!errorConverter.canBeHandled(event.exception)) return
val errorParams = errorConverter.convert(error).toMutableMap()
errorParams["Category"] = event.category
errorParams["Event"] = event.event
errorParams.putAll(event.params)
send(error, errorParams)
}
event is Shop.Purchased -> {
send(FirebaseAnalytics.Event.PURCHASE, event.params)
}
else -> {
super.send(event)
}
}
val errorParams = errorConverter.convert(event.exception).toMutableMap()
errorParams.putAll(event.params)
client.logException(event.exception, event.params)
}
override fun send(error: Throwable, params: Map<String, String>) {
client.logErrorEvent(error, params)
override fun sendErrorEvent(event: AnalyticsEvent) {
send(event)
}
companion object {

View file

@ -4,14 +4,15 @@ import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.crashlytics.recordException
import com.google.firebase.ktx.Firebase
import com.tangem.core.analytics.api.ErrorEventLogger
import com.tangem.core.analytics.api.ExceptionLogger
import com.tangem.core.analytics.api.EventLogger
/**
[REDACTED_AUTHOR]
*/
interface FirebaseAnalyticsClient : EventLogger, ErrorEventLogger
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger
internal class FirebaseClient : FirebaseAnalyticsClient {
@ -27,11 +28,12 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
)
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
eventConverter.convertEventParams(params)
.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
fbCrashlytics.recordException(error)
// TODO [REDACTED_TASK_KEY] fix recordException usages in the app
override fun logException(error: Throwable, params: Map<String, String>) {
fbCrashlytics.recordException(error) {
eventConverter.convertEventParams(params)
.forEach { key(it.key, it.value) }
}
}
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())

View file

@ -16,7 +16,7 @@ internal class FirebaseLogClient(
logger.logEvent(event, params)
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
logger.logErrorEvent(error, params)
override fun logException(error: Throwable, params: Map<String, String>) {
logger.logException(error, params)
}
}

View file

@ -10,11 +10,13 @@ import com.tangem.common.authentication.AuthenticationManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.Config
import com.tangem.common.services.secure.SecureStorage
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.crypto.bip39.Wordlist
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.data.card.sdk.CardSdkProvider
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
@ -32,8 +34,9 @@ import javax.inject.Singleton
*/
@Singleton
internal class DefaultCardSdkProvider @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
) : CardSdkProvider, CardSdkOwner {
private val observer = Observer()
@ -46,7 +49,12 @@ internal class DefaultCardSdkProvider @Inject constructor(
override fun register(activity: FragmentActivity) = runBlocking(dispatchers.mainImmediate) {
if (activity.isDestroyed || activity.isFinishing || activity.isChangingConfigurations) {
val message = "Tangem SDK owner registration skipped: activity is destroyed or finishing"
analyticsEventHandler.send(TangemSdkWarningEvent(message))
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = IllegalStateException(message),
params = errorParams,
),
)
Log.info { message }
return@runBlocking
}
@ -64,14 +72,24 @@ internal class DefaultCardSdkProvider @Inject constructor(
private fun tryToRegisterWithForegroundActivity(): TangemSdk = runBlocking(dispatchers.mainImmediate) {
val warning = "Tangem SDK holder is null, trying to recreate it with foreground activity"
analyticsEventHandler.send(TangemSdkWarningEvent(warning))
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = IllegalStateException(warning),
params = errorParams,
),
)
Log.warning { warning }
val activity = foregroundActivityObserver.foregroundActivity
if (activity == null) {
val error = "Tangem SDK holder is null and foreground activity is null"
analyticsEventHandler.send(TangemSdkWarningEvent(error))
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = IllegalStateException(error),
params = errorParams,
),
)
Log.error { error }
error(error)
}
@ -82,7 +100,12 @@ internal class DefaultCardSdkProvider @Inject constructor(
if (sdk == null) {
val error = "Tangem SDK is null after re-registering with foreground activity"
analyticsEventHandler.send(TangemSdkWarningEvent(error))
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = IllegalStateException(error),
params = errorParams,
),
)
Log.error { error }
error(error)
}
@ -108,7 +131,9 @@ internal class DefaultCardSdkProvider @Inject constructor(
authenticationManager = authenticationManager,
keystoreManager = keystoreManager,
wordlist = Wordlist.getWordlist(activity),
config = config,
config = config.apply {
isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled
},
)
holder = Holder(
@ -170,5 +195,10 @@ internal class DefaultCardSdkProvider @Inject constructor(
),
),
)
val errorParams = mapOf(
"Category" to "Tangem SDK",
"Event" to "Warning",
)
}
}

View file

@ -1,9 +0,0 @@
package com.tangem.tap.data
import com.tangem.core.analytics.models.AnalyticsEvent
internal class TangemSdkWarningEvent(message: String) : AnalyticsEvent(
category = "Tangem SDK",
event = "Warning",
error = IllegalStateException(message),
)

View file

@ -7,6 +7,7 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
import dagger.Provides
@ -67,6 +68,7 @@ internal object ManageTokensDomainModule {
networksRepository: NetworksRepository,
derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
): SaveManagedTokensUseCase {
return SaveManagedTokensUseCase(
customTokensRepository = customTokensRepository,
@ -75,6 +77,7 @@ internal object ManageTokensDomainModule {
networksRepository = networksRepository,
derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
)
}

View file

@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.markets.*
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
@ -57,12 +58,16 @@ object MarketsDomainModule {
marketsTokenRepository: MarketsTokenRepository,
currenciesRepository: CurrenciesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
): SaveMarketTokensUseCase {
return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository,
currenciesRepository = currenciesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
)
}

View file

@ -0,0 +1,40 @@
package com.tangem.tap.di.domain
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object NFTDomainModule {
@Provides
@Singleton
fun providesGetNFTCollectionsUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
): GetNFTCollectionsUseCase {
return GetNFTCollectionsUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
)
}
@Provides
@Singleton
fun providesFetchNFTCollectionsUseCase(
currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository,
): FetchNFTCollectionsUseCase {
return FetchNFTCollectionsUseCase(
currenciesRepository = currenciesRepository,
nftRepository = nftRepository,
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di.domain
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
import com.tangem.domain.onramp.*
import com.tangem.domain.onramp.repositories.HotCryptoRepository
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
@ -242,4 +243,10 @@ internal object OnrampDomainModule {
fun provideFetchHotCryptoUseCase(hotCryptoRepository: HotCryptoRepository): FetchHotCryptoUseCase {
return FetchHotCryptoUseCase(hotCryptoRepository)
}
@Provides
@Singleton
fun provideGetLegacyTopUpUrlUseCase(legacyTopUpRepository: LegacyTopUpRepository): GetLegacyTopUpUrlUseCase {
return GetLegacyTopUpUrlUseCase(legacyTopUpRepository)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -209,4 +210,12 @@ internal object StakingDomainModule {
fun provideGetStakingIntegrationIdUseCase(stakingRepository: StakingRepository): GetStakingIntegrationIdUseCase {
return GetStakingIntegrationIdUseCase(stakingRepository)
}
@Provides
@Singleton
fun provideCheckAccountInitializedUseCase(
walletManagersFacade: WalletManagersFacade,
): CheckAccountInitializedUseCase {
return CheckAccountInitializedUseCase(walletManagersFacade)
}
}

View file

@ -27,10 +27,14 @@ internal object TokensDomainModule {
fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
)
}

View file

@ -10,7 +10,10 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
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.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -18,6 +21,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Module
@InstallIn(SingletonComponent::class)
internal object WalletsDomainModule {
@ -165,4 +169,22 @@ internal object WalletsDomainModule {
fun providesSeedPhraseNotificationUseCase(walletsRepository: WalletsRepository): SeedPhraseNotificationUseCase {
return SeedPhraseNotificationUseCase(walletsRepository = walletsRepository)
}
@Provides
@Singleton
fun provideGetCardImageUseCase(onlineCardVerifier: OnlineCardVerifier): GetCardImageUseCase {
return GetCardImageUseCase(verifier = onlineCardVerifier)
}
@Provides
@Singleton
fun providesIsWalletNFTEnabledSyncUseCase(
walletsRepository: WalletsRepository,
nftFeatureToggles: NFTFeatureToggles,
): IsWalletNFTEnabledSyncUseCase {
return IsWalletNFTEnabledSyncUseCase(
walletsRepository = walletsRepository,
nftFeatureToggles = nftFeatureToggles,
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.routing
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.tap.routing.ProxyAppRouter
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
@ -17,8 +18,15 @@ internal object AppRouterModule {
@Provides
@Singleton
fun provideAppRouter(config: AppRouterConfig, dispatchers: CoroutineDispatcherProvider): AppRouter =
ProxyAppRouter(config, dispatchers)
fun provideAppRouter(
config: AppRouterConfig,
dispatchers: CoroutineDispatcherProvider,
analyticsExceptionHandler: AnalyticsExceptionHandler,
): AppRouter = ProxyAppRouter(
config = config,
dispatchers = dispatchers,
analyticsExceptionHandler = analyticsExceptionHandler,
)
@Provides
@Singleton

View file

@ -6,14 +6,17 @@ 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.CardVerifyAndGetInfo
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): String {
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
@ -28,7 +31,7 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result<CardVerifyAndGetInf
}
}
return when (val cardInfoResult = cardInfo ?: OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
return when (val cardInfoResult = cardInfo ?: onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = cardInfoResult.data.artwork?.id
if (artworkId.isNullOrEmpty()) {

View file

@ -7,6 +7,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
@ -34,7 +35,12 @@ internal object UseCaseScanProcessor {
ifLeft = {
val error = scanCardExceptionConverter.convertBack(it)
Analytics.send(Basic.ScanError(error))
Analytics.sendException(
ExceptionAnalyticsEvent(
exception = error,
params = mapOf("Event" to "Scan"),
),
)
CompletionResult.Failure(error)
},
ifRight = { CompletionResult.Success(it) },
@ -97,7 +103,12 @@ internal object UseCaseScanProcessor {
-> {
val error = scanCardExceptionConverter.convertBack(exception)
Analytics.send(Basic.ScanError(error))
Analytics.sendException(
ExceptionAnalyticsEvent(
exception = error,
params = mapOf("Event" to "Scan"),
),
)
onFailure(error)
}
}

View file

@ -14,7 +14,7 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.res.getStringSafe
import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath
@ -224,7 +224,12 @@ internal class DefaultTangemSdkManager(
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
if (result is CompletionResult.Failure) {
(result.error as? TangemSdkError)?.let { error ->
Analytics.send(Basic.ScanError(error))
Analytics.sendException(
ExceptionAnalyticsEvent(
exception = error,
params = mapOf("Event" to "Scan"),
),
)
}
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.common.routing.AppRoute
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
@ -61,7 +60,11 @@ class WalletConnectMiddleware {
if (uri != null && isWalletConnectUri(uri)) {
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
store.dispatchNavigationAction {
push(
AppRoute.QrScanning(AppRoute.QrScanning.Source.WALLET_CONNECT),
)
}
}
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {

View file

@ -4,7 +4,9 @@ import androidx.compose.runtime.Stable
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.common.util.cardTypesResolver
@ -30,6 +32,8 @@ internal class SecurityModeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
private val analyticsEventHandler: AnalyticsEventHandler,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : Model() {
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
@ -85,14 +89,19 @@ internal class SecurityModeModel @Inject constructor(
val paramValue = AnalyticsParam.SecurityMode.from(selectedOption)
when (result) {
is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
analyticsEventHandler.send(Settings.CardSettings.SecurityModeChanged(paramValue))
store.dispatchNavigationAction(AppRouter::pop)
}
is CompletionResult.Failure -> {
val error = result.error
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = error,
params = mapOf("Event" to "Security Mode Changed"),
),
)
}
}
else -> Unit

View file

@ -21,7 +21,7 @@ object ClipboardOrScanQrDialog {
}
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
store.dispatchNavigationAction {
push(AppRoute.QrScanning(source = SourceType.WALLET_CONNECT))
push(AppRoute.QrScanning(source = AppRoute.QrScanning.Source.WALLET_CONNECT))
}
}
setOnDismissListener {

View file

@ -19,7 +19,6 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
import com.tangem.tap.common.analytics.events.IntroductionProcess
@ -44,12 +43,12 @@ import javax.inject.Inject
internal class HomeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val scanCardProcessor: ScanCardProcessor,
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
private val saveWalletUseCase: SaveWalletUseCase,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
) : Model() {
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
@ -100,10 +99,7 @@ internal class HomeModel @Inject constructor(
}
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
val userWallet = UserWalletBuilder(
scanResponse = scanResponse,
generateWalletNameUseCase = generateWalletNameUseCase,
).build()
val userWallet = userWalletBuilderFactory.create(scanResponse = scanResponse).build()
if (userWallet == null) {
Timber.e("User wallet not created")

View file

@ -10,7 +10,6 @@ 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.wallets.builder.UserWalletBuilder
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.extensions.dispatchNavigationAction
@ -91,8 +90,9 @@ private suspend fun readCard() {
}
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build().guard {
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
val userWallet = userWalletBuilder.build().guard {
Timber.e("User wallet not created")
return@launch
}

View file

@ -14,7 +14,6 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
@ -70,6 +69,7 @@ object OnboardingHelper {
}
}
@Suppress("CyclomaticComplexMethod")
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
scanResponse.productType == ProductType.Ring ||
@ -104,7 +104,11 @@ object OnboardingHelper {
} else {
AppRoute.OnboardingOther
}
ProductType.Twins -> AppRoute.OnboardingTwins
ProductType.Twins -> if (featureToggles.isTwinRefactoringEnabled) {
AppRoute.Onboarding(scanResponse = scanResponse)
} else {
AppRoute.OnboardingTwins
}
ProductType.Start2Coin,
ProductType.Visa,
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
@ -250,8 +254,9 @@ object OnboardingHelper {
hasBackupError: Boolean,
alreadyCreatedWallet: UserWallet? = null,
) {
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
val userWalletBuilderFactory = store.inject(DaggerGraphState::userWalletBuilderFactory)
val userWallet = alreadyCreatedWallet ?: userWalletBuilderFactory.create(scanResponse)
.hasBackupError(hasBackupError)
.backupCardsIds(backupCardsIds?.toSet())
.build()

View file

@ -7,8 +7,8 @@ import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate
@ -26,16 +26,22 @@ import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
@Deprecated("Remove when navigation refactoring will be implemented")
class OnboardingManager(var scanResponse: ScanResponse) {
private val cardRepository: CardRepository = store.inject(DaggerGraphState::cardRepository)
private val cardRepository: CardRepository by lazy { store.inject(DaggerGraphState::cardRepository) }
private val onlineCardVerifier: OnlineCardVerifier by lazy { store.inject(DaggerGraphState::onlineCardVerifier) }
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
suspend fun loadArtworkUrl(): String {
val cardInfo = cardInfo
?: OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
?: onlineCardVerifier.getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
this.cardInfo = cardInfo
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo, onlineCardVerifier)
}
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {

View file

@ -26,9 +26,7 @@ import com.tangem.tap.store
import com.tangem.utils.extensions.stripZeroPlainString
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Deprecated("Use OnboardingNoteComponent")
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import android.net.Uri
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@ -12,6 +11,7 @@ import com.tangem.common.routing.AppRouter
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.toWrappedList
@ -24,7 +24,6 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.Artwork
@ -243,7 +242,9 @@ private suspend fun readCard(): CompletionResult<ScanResponse> {
}
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
return when (val cardInfo = OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
val onlineCardVerifier = store.inject(DaggerGraphState::onlineCardVerifier)
return when (val cardInfo = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = cardInfo.data.artwork?.id
if (artworkId.isNullOrEmpty()) {
@ -453,8 +454,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
store.dispatchOnMain(BackupAction.AddBackupCard.Success(result.data))
}
is CompletionResult.Failure -> {
val crashlytics = FirebaseCrashlytics.getInstance()
when (val error = result.error) {
is TangemSdkError.CardVerificationFailed -> {
Analytics.send(
@ -491,7 +490,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
GlobalAction.ShowDialog(BackupDialog.AttestationFailed),
)
}
else -> crashlytics.recordException(error)
else -> Analytics.sendException(ExceptionAnalyticsEvent(error))
}
}
}
@ -782,9 +781,10 @@ private suspend fun updateWallet(
}
private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet {
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
return requireNotNull(
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
value = userWalletBuilder
.backupCardsIds(backupState.backupCardIds.toSet())
.hasBackupError(backupState.hasBackupError)
.build(),

View file

@ -5,7 +5,7 @@ import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog

View file

@ -6,7 +6,6 @@ import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
@ -70,8 +69,10 @@ internal class SaveWalletMiddleware {
scope.launch {
val backupInfo = state.backupInfo ?: error("Backup info is null")
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
val userWallet = UserWalletBuilder(backupInfo.scanResponse, walletNameGenerateUseCase)
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory)
.create(scanResponse = backupInfo.scanResponse)
val userWallet = userWalletBuilder
.backupCardsIds(state.backupInfo.backupCardsIds)
.hasBackupError(hasBackupError)
.build()

View file

@ -11,10 +11,8 @@ import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics
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.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
import com.tangem.domain.wallets.legacy.unlockIfLockable
import com.tangem.tap.backupService
@ -118,9 +116,9 @@ internal class WelcomeMiddleware {
)
scanCardInternal { scanResponse ->
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build()
?: return@scanCardInternal
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
val userWallet = userWalletBuilder.build() ?: return@scanCardInternal
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
userWalletsListManager.save(userWallet, canOverride = true)

View file

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

View file

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

View file

@ -26,13 +26,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.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
@ -56,7 +56,6 @@ data class DaggerGraphState(
val generalUserWalletsListManager: UserWalletsListManager? = null,
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
val generateWalletNameUseCase: GenerateWalletNameUseCase? = null,
val cardRepository: CardRepository? = null,
val settingsRepository: SettingsRepository? = null,
val blockchainSDKFactory: BlockchainSDKFactory? = null,
@ -67,7 +66,6 @@ data class DaggerGraphState(
val shareManager: ShareManager? = null,
val appRouter: AppRouter? = null,
val transactionSignerFactory: TransactionSignerFactory? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null,
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
@ -77,4 +75,6 @@ data class DaggerGraphState(
val clipboardManager: ClipboardManager? = null,
val settingsManager: SettingsManager? = null,
val uiMessageSender: UiMessageSender? = null,
val onlineCardVerifier: OnlineCardVerifier? = null,
val userWalletBuilderFactory: UserWalletBuilder.Factory? = null,
) : StateType

View file

@ -1,8 +1,9 @@
package com.tangem.tap.routing
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.core.decompose.navigation.Router
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -15,6 +16,7 @@ import kotlin.reflect.KClass
internal class ProxyAppRouter(
private val config: AppRouterConfig,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : AppRouter {
private val routerScope: CoroutineScope
@ -76,7 +78,7 @@ internal class ProxyAppRouter(
override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) {
if (!isSuccess) {
FirebaseCrashlytics.getInstance().recordException(RuntimeException(errorMessage))
analyticsExceptionHandler.sendException(ExceptionAnalyticsEvent(RuntimeException(errorMessage)))
Timber.w(errorMessage)
with(receiver = config.snackbarHandler ?: return) {

View file

@ -2,6 +2,7 @@ package com.tangem.tap.routing.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.feature.qrscanning.QrScanningComponent
import com.tangem.feature.referral.api.ReferralComponent
import com.tangem.feature.stories.api.StoriesComponent
@ -15,8 +16,9 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.*
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
import com.tangem.features.send.api.SendComponent
import com.tangem.features.swap.SwapComponent
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.swap.SwapComponent
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent
@ -74,6 +76,8 @@ internal class ChildFactory @Inject constructor(
private val referralComponentFactory: ReferralComponent.Factory,
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
private val walletComponentFactory: WalletEntryComponent.Factory,
private val sendComponentFactoryV2: com.tangem.features.send.v2.api.SendComponent.Factory,
private val sendFeatureToggles: SendFeatureToggles,
private val testerRouter: TesterRouter,
private val routingFeatureToggles: RoutingFeatureToggles,
) {
@ -256,18 +260,33 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.Send -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
if (sendFeatureToggles.isSendV2Enabled) {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = com.tangem.features.send.v2.api.SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactoryV2,
)
} else {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
}
}
is AppRoute.Home -> {
createComponentChild(
@ -284,10 +303,14 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.QrScanning -> {
val source = when (route.source) {
AppRoute.QrScanning.Source.SEND -> SourceType.SEND
AppRoute.QrScanning.Source.WALLET_CONNECT -> SourceType.WALLET_CONNECT
}
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = QrScanningComponent.Params(
source = route.source,
source = source,
networkName = route.networkName,
),
componentFactory = qrScanningComponentFactory,
@ -400,18 +423,33 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() })
}
is AppRoute.Send -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
if (sendFeatureToggles.isSendV2Enabled) {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = com.tangem.features.send.v2.api.SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactoryV2,
)
} else {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
}
}
is AppRoute.AppSettings -> {
route.asComponentChild(
@ -483,10 +521,14 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { OnboardingWalletFragment() })
}
is AppRoute.QrScanning -> {
val source = when (route.source) {
AppRoute.QrScanning.Source.SEND -> SourceType.SEND
AppRoute.QrScanning.Source.WALLET_CONNECT -> SourceType.WALLET_CONNECT
}
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = QrScanningComponent.Params(
source = route.source,
source = source,
networkName = route.networkName,
),
componentFactory = qrScanningComponentFactory,