diff --git a/.gitignore b/.gitignore index 74ed0fd34b..6106311a37 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ local.properties # Google services +app/src/debug/google-services.json +app/src/internal/google-services.json +app/src/external/google-services.json # Gradle generated files .gradle diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e666baaa4d..c48f118fe4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,8 @@ dependencies { implementation(projects.domain.networks) implementation(projects.domain.quotes) implementation(projects.domain.notifications) + implementation(projects.domain.notifications.models) + implementation(projects.domain.notifications.toggles) implementation(projects.common) implementation(projects.common.routing) @@ -175,8 +177,6 @@ dependencies { implementation(projects.features.tokendetails.impl) implementation(projects.features.manageTokens.api) implementation(projects.features.manageTokens.impl) - implementation(projects.features.send.api) - implementation(projects.features.send.impl) implementation(projects.features.sendV2.api) implementation(projects.features.sendV2.impl) implementation(projects.features.qrScanning.api) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cac33d689c..06a868e0a4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -128,7 +128,7 @@ @@ -150,10 +150,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a20134ee4b..4ec5de6632 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a20134ee4be4d7e1e34c6f44089b3cb0713eacc3 +Subproject commit 4ec5de66321afa82c04104746015ea9e6bc9fe64 diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index ee95e2ba40..a005dd97cb 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -15,6 +15,7 @@ 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.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.issuers.IssuersConfigStorage @@ -33,7 +34,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.builder.UserWalletBuilder +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 @@ -141,5 +142,7 @@ interface ApplicationEntryPoint { fun getOnlineCardVerifier(): OnlineCardVerifier - fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory + fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory + + fun getApiConfigsManager(): ApiConfigsManager } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 66df0f2d73..557f69fdd0 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,18 +19,23 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle 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.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.DEEPLINK_KEY import com.tangem.core.deeplink.DeepLinksRegistry +import com.tangem.core.deeplink.WEBLINK_KEY 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 @@ -48,6 +53,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.walletconnect.components.WalletConnectFeatureToggles import com.tangem.google.GoogleServicesHelper import com.tangem.operations.backup.BackupService import com.tangem.sdk.api.BackupServiceHolder @@ -67,8 +73,10 @@ 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 +import com.tangem.tap.routing.utils.DeepLinkFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler +import com.tangem.utils.extensions.uriValidate import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -171,6 +179,18 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector + @Inject + internal lateinit var routingFeatureToggle: RoutingFeatureToggle + + @Inject + internal lateinit var deeplinkFactory: DeepLinkFactory + + @Inject + internal lateinit var walletConnectFeatureToggles: WalletConnectFeatureToggles + + @Inject + internal lateinit var urlOpener: UrlOpener + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -223,9 +243,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { sendStakingUnsubmittedHashes() checkGoogleServicesAvailability() - if (intent != null && savedInstanceState == null) { + if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) { // handle intent only on start, not on recreate - deepLinksRegistry.launch(intent) + handleDeepLink(intent = intent, isFromOnNewIntent = false) } lifecycle.addObserver(WindowObscurationObserver) @@ -341,7 +361,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets } intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler)) intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope)) - intentProcessor.addHandler(WalletConnectLinkIntentHandler()) + + if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { + intentProcessor.addHandler(WalletConnectLinkIntentHandler()) + } } private fun updateAppTheme(appThemeMode: AppThemeMode) { @@ -374,7 +397,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } if (intent != null) { - deepLinksRegistry.launch(intent) + handleDeepLink(intent = intent, isFromOnNewIntent = true) } } @@ -455,9 +478,37 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } } + if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) { + handleDeepLink(intent = intent, isFromOnNewIntent = false) + } + viewModel.checkForUnfinishedBackup() } + private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) { + if (routingFeatureToggle.isDeepLinkNavigationEnabled) { + val deepLinkExtras = intent.getStringExtra(DEEPLINK_KEY)?.toUri() + val webLink = intent.getStringExtra(WEBLINK_KEY) + + val receivedDeepLink = intent.data ?: deepLinkExtras + + when { + receivedDeepLink != null -> { + deeplinkFactory.handleDeeplink( + deeplinkUri = receivedDeepLink, + coroutineScope = lifecycleScope, + isFromOnNewIntent = isFromOnNewIntent, + ) + } + webLink?.uriValidate() == true -> { + urlOpener.openUrl(webLink) + } + } + } else { + deepLinksRegistry.launch(intent) + } + } + private fun observePolkadotAccountHealthCheck() { lifecycleScope.launch { getPolkadotCheckHasResetUseCase() diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index ed0018b6d9..0114cc78ba 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -30,6 +30,7 @@ import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.data.card.TransactionSignerFactory import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfig @@ -52,7 +53,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.builder.UserWalletBuilder +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 @@ -225,8 +226,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat private val onlineCardVerifier: OnlineCardVerifier get() = entryPoint.getOnlineCardVerifier() - private val userWalletBuilderFactory: UserWalletBuilder.Factory - get() = entryPoint.getUserWalletBuilderFactory() + private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory + get() = entryPoint.getColdUserWalletBuilderFactory() + + private val apiConfigsManager: ApiConfigsManager + get() = entryPoint.getApiConfigsManager() // endregion @@ -275,6 +279,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat } fun init() { + apiConfigsManager.initialize() + store = createReduxStore() tangemAppLoggerInitializer.initialize() @@ -285,12 +291,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat // We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them. runBlocking { awaitAll( - async { - featureTogglesManager.init() - }, - async { - excludedBlockchainsManager.init() - }, + async { featureTogglesManager.init() }, + async { excludedBlockchainsManager.init() }, ) initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) } @@ -366,7 +368,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat settingsManager = settingsManager, uiMessageSender = uiMessageSender, onlineCardVerifier = onlineCardVerifier, - userWalletBuilderFactory = userWalletBuilderFactory, + coldUserWalletBuilderFactory = coldUserWalletBuilderFactory, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index fe2add9b71..a90e193a92 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -104,7 +104,6 @@ sealed class AnalyticsParam { const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" const val FIRMWARE = "Firmware" - const val USER_WALLET_ID = "User Wallet ID" const val CURRENCY = "Currency" const val ERROR_DESCRIPTION = "Error Description" const val ERROR_CODE = "Error Code" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index 2aa36c5413..d28504b612 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -1,14 +1,23 @@ package com.tangem.tap.common.analytics.handlers.amplitude import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.AnalyticsUserIdHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AmplitudeAnalyticsHandler( private val client: AmplitudeAnalyticsClient, -) : AnalyticsHandler { +) : AnalyticsHandler, AnalyticsUserIdHandler { override fun id(): String = ID + override fun setUserId(userId: String) { + client.setUserId(userId) + } + + override fun clearUserId() { + client.clearUserId() + } + override fun send(eventId: String, params: Map) { client.logEvent(eventId, params) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt index 74cec2eaf2..a5ffec919c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt @@ -4,13 +4,14 @@ import android.app.Application import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient import com.tangem.core.analytics.api.EventLogger +import com.tangem.core.analytics.api.UserIdHolder import com.tangem.utils.converter.Converter import org.json.JSONObject /** [REDACTED_AUTHOR] */ -interface AmplitudeAnalyticsClient : EventLogger +interface AmplitudeAnalyticsClient : EventLogger, UserIdHolder internal class AmplitudeClient( application: Application, @@ -24,6 +25,14 @@ internal class AmplitudeClient( client.enableForegroundTracking(application) } + override fun setUserId(userId: String) { + client.setUserId(userId) + } + + override fun clearUserId() { + client.setUserId(null) + } + override fun logEvent(event: String, params: Map) { client.logEvent(event, ParamsToJSONObjectConverter().convert(params)) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt index f60d2edfcf..f24a5925a3 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt @@ -12,6 +12,16 @@ internal class AmplitudeLogClient( private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter) + private var userId: String? = null + + override fun setUserId(userId: String) { + this.userId = userId + } + + override fun clearUserId() { + this.userId = null + } + override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index 298874dcef..fbea91a9f4 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics.handlers.firebase import com.tangem.core.analytics.api.AnalyticsErrorHandler import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.api.AnalyticsExceptionHandler +import com.tangem.core.analytics.api.AnalyticsUserIdHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -10,12 +11,20 @@ import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, -) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler { +) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler, AnalyticsUserIdHandler { private val errorConverter = AnalyticsErrorConverter() override fun id(): String = ID + override fun setUserId(userId: String) { + client.setUserId(userId) + } + + override fun clearUserId() { + client.clearUserId() + } + override fun send(eventId: String, params: Map) { client.logEvent(eventId, params) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 06ccf50709..6268ebf230 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -8,11 +8,12 @@ import com.google.firebase.crashlytics.recordException import com.google.firebase.ktx.Firebase import com.tangem.core.analytics.api.ExceptionLogger import com.tangem.core.analytics.api.EventLogger +import com.tangem.core.analytics.api.UserIdHolder /** [REDACTED_AUTHOR] */ -interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger +interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger, UserIdHolder internal class FirebaseClient : FirebaseAnalyticsClient { @@ -21,6 +22,14 @@ internal class FirebaseClient : FirebaseAnalyticsClient { private val eventConverter = FirebaseAnalyticsEventConverter() + override fun setUserId(userId: String) { + Firebase.analytics.setUserId(userId) + } + + override fun clearUserId() { + Firebase.analytics.setUserId(null) + } + override fun logEvent(event: String, params: Map) { fbAnalytics.logEvent( eventConverter.convertEventName(event), diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt index 4b818bdf51..391b7e7470 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt @@ -11,6 +11,15 @@ internal class FirebaseLogClient( ) : FirebaseAnalyticsClient { private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter) + private var userId: String? = null + + override fun setUserId(userId: String) { + this.userId = userId + } + + override fun clearUserId() { + this.userId = null + } override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 42a7f46c75..f0f3326917 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -40,9 +40,6 @@ class CardContextInterceptor( params[AnalyticsParam.BATCH] = card.batchId params[AnalyticsParam.PRODUCT_TYPE] = getProductType() params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue - if (userWalletId != null) { - params[AnalyticsParam.USER_WALLET_ID] = userWalletId.stringValue - } ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let { params[AnalyticsParam.CURRENCY] = it.value diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt index 8e79d07381..d8dc74443d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt @@ -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.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor /** @@ -12,6 +13,11 @@ import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterc * Sets the new context */ fun Analytics.setContext(scanResponse: ScanResponse) { + val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + if (userWalletId != null) { + setUserId(userWalletId.stringValue) + } + addParamsInterceptor(LinkedCardContextInterceptor(scanResponse)) } @@ -19,6 +25,7 @@ fun Analytics.setContext(scanResponse: ScanResponse) { * Erases the context */ fun Analytics.eraseContext() { + clearUserId() removeParamsInterceptor(LinkedCardContextInterceptor.id()) } @@ -26,6 +33,11 @@ fun Analytics.eraseContext() { * Adds a new context and keeps a previous context as the parent of the new one */ fun Analytics.addContext(scanResponse: ScanResponse) { + val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + if (userWalletId != null) { + setUserId(userWalletId.stringValue) + } + val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 448ae3e36a..0396cf84fc 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -1,34 +1,5 @@ package com.tangem.tap.common.extensions -import android.graphics.Bitmap -import android.graphics.Color -import com.google.zxing.BarcodeFormat -import com.google.zxing.EncodeHintType -import com.google.zxing.qrcode.QRCodeWriter -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import java.util.Hashtable - -@Suppress("MagicNumber") -fun String.toQrCode(): Bitmap { - val hintMap = Hashtable() - hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage - hintMap[EncodeHintType.MARGIN] = 2 - - val qrCodeWriter = QRCodeWriter() - - val size = 256 - - val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap) - val width = bitMatrix.width - val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565) - for (x in 0 until width) { - for (y in 0 until width) { - bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE) - } - } - return bmp -} - fun String.removePrefixOrNull(prefix: String): String? = when { startsWith(prefix) -> substring(prefix.length) else -> null diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index d16033568b..acf3104c3f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -6,7 +6,7 @@ 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.tokens.model.CryptoCurrency +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 diff --git a/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt b/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt index 8f984c76f0..ea9a016793 100644 --- a/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt +++ b/app/src/main/java/com/tangem/tap/common/pushes/TangemPushNotificationService.kt @@ -16,6 +16,8 @@ import coil.request.ImageRequest import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.tangem.core.deeplink.DEEPLINK_KEY +import com.tangem.core.deeplink.WEBLINK_KEY +import com.tangem.core.deeplink.converter.PayloadToDeeplinkConverter import com.tangem.domain.common.LogConfig import com.tangem.tap.MainActivity import com.tangem.tap.common.images.createCoilImageLoader @@ -36,9 +38,11 @@ internal class TangemPushNotificationService : FirebaseMessagingService() { val notification = message.notification ?: return val channelId = notification.channelId ?: TANGEM_CHANNEL_ID + val deeplink = PayloadToDeeplinkConverter.convert(message.data) val intent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra(DEEPLINK_KEY, message.data[DEEPLINK_KEY]) + putExtra(DEEPLINK_KEY, deeplink) + putExtra(WEBLINK_KEY, message.data[WEBLINK_KEY]) putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index e7f1d6613d..a75849544c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -10,6 +10,7 @@ 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.redux.AppState import com.tangem.tap.network.exchangeServices.CardExchangeRules @@ -59,7 +60,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { scope.launch { val scanResponseProvider: () -> ScanResponse? = { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - userWalletsListManager.selectedUserWalletSync?.scanResponse + userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY] } val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index 1eea5ec1a0..a950925df9 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.legacy import com.tangem.domain.apptheme.model.AppThemeMode 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 @@ -36,7 +37,8 @@ internal object LegacyMiddleware { ) store.dispatchWithMain( DetailsAction.PrepareScreen( - scanResponse = selectedUserWallet.scanResponse, + // TODO [REDACTED_TASK_KEY] + scanResponse = selectedUserWallet.requireColdWallet().scanResponse, initializedAppSettingsState = initializedAppSettingsStateContent, ), ) diff --git a/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt index 02a9b8be72..6167cea5a0 100644 --- a/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt +++ b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt @@ -3,8 +3,9 @@ package com.tangem.tap.data import com.google.firebase.messaging.FirebaseMessaging import com.tangem.utils.notifications.PushNotificationsTokenProvider import kotlinx.coroutines.tasks.await +import javax.inject.Inject -internal class FirebasePushNotificationsTokenProvider : PushNotificationsTokenProvider { +internal class FirebasePushNotificationsTokenProvider @Inject constructor() : PushNotificationsTokenProvider { override suspend fun getToken(): String { return FirebaseMessaging.getInstance().token.await() } diff --git a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt index 34411415bf..788367340b 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.di.data -import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.sdk.api.TangemSdkManager @@ -21,9 +21,14 @@ internal object CardDataModule { fun providesDerivationsRepository( tangemSdkManager: TangemSdkManager, userWalletsStore: UserWalletsStore, - excludedBlockchains: ExcludedBlockchains, + networkFactory: NetworkFactory, dispatchers: CoroutineDispatcherProvider, ): DerivationsRepository { - return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, excludedBlockchains, dispatchers) + return DefaultDerivationsRepository( + tangemSdkManager = tangemSdkManager, + userWalletsStore = userWalletsStore, + networkFactory = networkFactory, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index b55ef9a8f7..c78eeae98f 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -10,8 +10,6 @@ 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.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -70,10 +68,8 @@ internal object ManageTokensDomainModule { customTokensRepository: CustomTokensRepository, walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, derivationsRepository: DerivationsRepository, stakingRepository: StakingRepository, - quotesRepository: QuotesRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, @@ -83,10 +79,8 @@ internal object ManageTokensDomainModule { customTokensRepository = customTokensRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, derivationsRepository = derivationsRepository, stakingRepository = stakingRepository, - quotesRepository = quotesRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 4889626ff1..95b8accbf9 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -13,8 +13,6 @@ 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.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import dagger.Module import dagger.Provides @@ -54,16 +52,8 @@ object MarketsDomainModule { @Provides @Singleton - fun provideGetTokenQuotesUseCase( - quotesRepository: QuotesRepository, - singleQuoteSupplier: SingleQuoteSupplier, - tokensFeatureToggles: TokensFeatureToggles, - ): GetCurrencyQuotesUseCase { - return GetCurrencyQuotesUseCase( - quotesRepository = quotesRepository, - singleQuoteSupplier = singleQuoteSupplier, - tokensFeatureToggles = tokensFeatureToggles, - ) + fun provideGetTokenQuotesUseCase(singleQuoteSupplier: SingleQuoteSupplier): GetCurrencyQuotesUseCase { + return GetCurrencyQuotesUseCase(singleQuoteSupplier = singleQuoteSupplier) } @Provides @@ -72,9 +62,7 @@ object MarketsDomainModule { derivationsRepository: DerivationsRepository, marketsTokenRepository: MarketsTokenRepository, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, - quotesRepository: QuotesRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, @@ -84,9 +72,7 @@ object MarketsDomainModule { derivationsRepository = derivationsRepository, marketsTokenRepository = marketsTokenRepository, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, stakingRepository = stakingRepository, - quotesRepository = quotesRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, diff --git a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt index 090a030029..07c16b3c3f 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt @@ -1,9 +1,12 @@ package com.tangem.tap.di.domain +import com.tangem.domain.networks.single.SingleNetworkStatusSupplier import com.tangem.domain.nft.* import com.tangem.domain.nft.repository.NFTRepository +import com.tangem.domain.quotes.single.SingleQuoteFetcher +import com.tangem.domain.quotes.single.SingleQuoteSupplier import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -72,10 +75,11 @@ internal object NFTDomainModule { @Provides @Singleton - fun providesGetNFTNetworkStatusUseCase(networksRepository: NetworksRepository): GetNFTNetworkStatusUseCase = - GetNFTNetworkStatusUseCase( - networksRepository = networksRepository, - ) + fun providesGetNFTNetworkStatusUseCase( + singleNetworkStatusSupplier: SingleNetworkStatusSupplier, + ): GetNFTNetworkStatusUseCase { + return GetNFTNetworkStatusUseCase(singleNetworkStatusSupplier = singleNetworkStatusSupplier) + } @Provides @Singleton @@ -83,4 +87,53 @@ internal object NFTDomainModule { GetNFTExploreUrlUseCase( nftRepository = nftRepository, ) + + @Provides + @Singleton + fun provideGetNFTPriceUseCase( + nftRepository: NFTRepository, + singleQuoteSupplier: SingleQuoteSupplier, + ): GetNFTPriceUseCase { + return GetNFTPriceUseCase(nftRepository, singleQuoteSupplier) + } + + @Provides + @Singleton + fun provideFetchNFTPriceUseCase( + nftRepository: NFTRepository, + singleQuoteFetcher: SingleQuoteFetcher, + ): FetchNFTPriceUseCase { + return FetchNFTPriceUseCase(nftRepository, singleQuoteFetcher) + } + + @Provides + @Singleton + fun provideEnableWalletNFTUseCase(walletsRepository: WalletsRepository): EnableWalletNFTUseCase { + return EnableWalletNFTUseCase(walletsRepository) + } + + @Provides + @Singleton + fun provideDisableWalletNFTUseCase( + walletsRepository: WalletsRepository, + nftRepository: NFTRepository, + currenciesRepository: CurrenciesRepository, + ): DisableWalletNFTUseCase { + return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository) + } + + @Provides + @Singleton + fun provideGetWalletNFTEnabledUseCase(walletsRepository: WalletsRepository): GetWalletNFTEnabledUseCase { + return GetWalletNFTEnabledUseCase(walletsRepository) + } + + @Provides + @Singleton + fun provideClearNFTCacheUseCase( + nftRepository: NFTRepository, + currenciesRepository: CurrenciesRepository, + ): ObserveAndClearNFTCacheIfNeedUseCase { + return ObserveAndClearNFTCacheIfNeedUseCase(nftRepository, currenciesRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt index c6f217e01c..c7b61fd1e0 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt @@ -1,10 +1,10 @@ package com.tangem.tap.di.domain -import com.tangem.domain.notifications.GetApplicationIdUseCase -import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase -import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase -import com.tangem.domain.notifications.SendPushTokenUseCase +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.domain.notifications.* import com.tangem.domain.notifications.repository.NotificationsRepository +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles +import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles import com.tangem.utils.notifications.PushNotificationsTokenProvider import dagger.Module import dagger.Provides @@ -28,12 +28,10 @@ internal object NotificationsDomainModule { @Singleton fun providesSendPushTokenUseCase( notificationsRepository: NotificationsRepository, - getApplicationIdUseCase: GetApplicationIdUseCase, pushNotificationsTokenProvider: PushNotificationsTokenProvider, ): SendPushTokenUseCase { return SendPushTokenUseCase( notificationsRepository = notificationsRepository, - getApplicationIdUseCase = getApplicationIdUseCase, pushNotificationsTokenProvider = pushNotificationsTokenProvider, ) } @@ -57,4 +55,18 @@ internal object NotificationsDomainModule { notificationsRepository = notificationsRepository, ) } + + @Provides + @Singleton + fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles { + return DefaultNotificationsFeatureToggles(featureTogglesManager = featureTogglesManager) + } + + @Provides + @Singleton + fun provideGetNetworksAvailableForNotifications( + notificationsRepository: NotificationsRepository, + ): GetNetworksAvailableForNotificationsUseCase { + return GetNetworksAvailableForNotificationsUseCase(notificationsRepository = notificationsRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index ff23a2c9e2..185840f10a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.networks.repository.NetworksRepository import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusSupplier import com.tangem.domain.promo.PromoRepository @@ -18,7 +19,9 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations -import com.tangem.domain.tokens.repository.* +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.walletmanager.WalletManagersFacade import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -37,9 +40,7 @@ internal object TokensDomainModule { @Singleton fun provideAddCryptoCurrenciesUseCase( currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, - quotesRepository: QuotesRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, @@ -47,9 +48,7 @@ internal object TokensDomainModule { ): AddCryptoCurrenciesUseCase { return AddCryptoCurrenciesUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, stakingRepository = stakingRepository, - quotesRepository = quotesRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, @@ -61,8 +60,6 @@ internal object TokensDomainModule { @Singleton fun provideFetchTokenListUseCase( currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -71,8 +68,6 @@ internal object TokensDomainModule { ): FetchTokenListUseCase { return FetchTokenListUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, - quotesRepository = quotesRepository, stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, @@ -121,8 +116,8 @@ internal object TokensDomainModule { fun provideGetCurrencyUseCase( baseCurrencyStatusOperations: BaseCurrencyStatusOperations, dispatchers: CoroutineDispatcherProvider, - ): GetCurrencyStatusUpdatesUseCase { - return GetCurrencyStatusUpdatesUseCase( + ): GetSingleCryptoCurrencyStatusUseCase { + return GetSingleCryptoCurrencyStatusUseCase( currencyStatusOperations = baseCurrencyStatusOperations, dispatchers = dispatchers, ) @@ -147,7 +142,6 @@ internal object TokensDomainModule { fun provideGetCurrencyWarningsUseCase( walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, currencyChecksRepository: CurrencyChecksRepository, dispatchers: CoroutineDispatcherProvider, baseCurrencyStatusOperations: BaseCurrencyStatusOperations, @@ -155,31 +149,16 @@ internal object TokensDomainModule { return GetCurrencyWarningsUseCase( walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, + dispatchers = dispatchers, currencyChecksRepository = currencyChecksRepository, - dispatchers = dispatchers, currencyStatusOperations = baseCurrencyStatusOperations, ) } - @Provides - @Singleton - fun provideGetPrimaryCurrencyUseCase( - currencyStatusOperations: BaseCurrencyStatusOperations, - dispatchers: CoroutineDispatcherProvider, - ): GetPrimaryCurrencyStatusUpdatesUseCase { - return GetPrimaryCurrencyStatusUpdatesUseCase( - currencyStatusOperations = currencyStatusOperations, - dispatchers = dispatchers, - ) - } - @Provides @Singleton fun provideFetchCurrencyStatusUseCase( currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -188,8 +167,6 @@ internal object TokensDomainModule { ): FetchCurrencyStatusUseCase { return FetchCurrencyStatusUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, - quotesRepository = quotesRepository, stakingRepository = stakingRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, @@ -202,8 +179,6 @@ internal object TokensDomainModule { @Singleton fun provideFetchCardTokenListUseCase( currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteFetcher: MultiQuoteFetcher, @@ -212,8 +187,6 @@ internal object TokensDomainModule { ): FetchCardTokenListUseCase { return FetchCardTokenListUseCase( currenciesRepository = currenciesRepository, - networksRepository = networksRepository, - quotesRepository = quotesRepository, stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteFetcher = multiQuoteFetcher, @@ -222,14 +195,6 @@ internal object TokensDomainModule { ) } - @Provides - @Singleton - fun providesGetCryptoCurrencyStatusSyncUseCase( - currencyStatusOperations: BaseCurrencyStatusOperations, - ): GetCryptoCurrencyStatusSyncUseCase { - return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations) - } - @Provides @Singleton fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase { @@ -328,14 +293,10 @@ internal object TokensDomainModule { @Provides @Singleton fun provideUpdateDelayedCurrencyStatusUseCase( - networksRepository: NetworksRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): UpdateDelayedNetworkStatusUseCase { return UpdateDelayedNetworkStatusUseCase( - networksRepository = networksRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -385,15 +346,11 @@ internal object TokensDomainModule { @Singleton fun provideRefreshMultiCurrencyWalletQuotesUseCase( currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, multiQuoteFetcher: MultiQuoteFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): RefreshMultiCurrencyWalletQuotesUseCase { return RefreshMultiCurrencyWalletQuotesUseCase( currenciesRepository = currenciesRepository, - quotesRepository = quotesRepository, multiQuoteFetcher = multiQuoteFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -411,9 +368,7 @@ internal object TokensDomainModule { fun provideBaseCurrenciesStatusesOperations( tokensFeatureToggles: TokensFeatureToggles, currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, quotesRepositoryV2: QuotesRepositoryV2, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, @@ -425,9 +380,7 @@ internal object TokensDomainModule { ): BaseCurrenciesStatusesOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, - quotesRepository = quotesRepository, quotesRepositoryV2 = quotesRepositoryV2, - networksRepository = networksRepository, stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, @@ -445,9 +398,7 @@ internal object TokensDomainModule { fun provideBaseCurrencyStatusOperations( tokensFeatureToggles: TokensFeatureToggles, currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, quotesRepositoryV2: QuotesRepositoryV2, - networksRepository: NetworksRepository, stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, @@ -459,9 +410,7 @@ internal object TokensDomainModule { ): BaseCurrencyStatusOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, - quotesRepository = quotesRepository, quotesRepositoryV2 = quotesRepositoryV2, - networksRepository = networksRepository, stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index b0b9e8f8ad..ea48365cb7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -4,9 +4,7 @@ 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.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.* @@ -43,7 +41,6 @@ internal object TransactionDomainModule { transactionRepository: TransactionRepository, walletManagersFacade: WalletManagersFacade, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): SendTransactionUseCase { return SendTransactionUseCase( demoConfig = DemoConfig(), @@ -51,7 +48,6 @@ internal object TransactionDomainModule { transactionRepository = transactionRepository, walletManagersFacade = walletManagersFacade, singleNetworkStatusFetcher = singleNetworkStatusFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -61,17 +57,13 @@ internal object TransactionDomainModule { cardSdkConfigRepository: CardSdkConfigRepository, walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, - networksRepository: NetworksRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, - tokensFeatureToggles: TokensFeatureToggles, ): AssociateAssetUseCase { return AssociateAssetUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, - networksRepository = networksRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, - tokensFeatureToggles = tokensFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 4096521b75..684a7f0cf7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -1,11 +1,14 @@ package com.tangem.tap.di.domain import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate +import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository @@ -28,6 +31,17 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object WalletsDomainModule { + @Provides + fun providesUserWalletsSyncDelegate( + userWalletsListManager: UserWalletsListManager, + dispatchers: CoroutineDispatcherProvider, + ): UserWalletsSyncDelegate { + return DefaultUserWalletsSyncDelegate( + userWalletsListManager = userWalletsListManager, + dispatchers = dispatchers, + ) + } + @Provides @Singleton fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase { @@ -102,10 +116,13 @@ internal object WalletsDomainModule { @Provides @Singleton fun providesRenameWalletUseCase( - userWalletsListManager: UserWalletsListManager, - dispatchers: CoroutineDispatcherProvider, + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, ): RenameWalletUseCase { - return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers) + return RenameWalletUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) } @Provides @@ -197,4 +214,68 @@ internal object WalletsDomainModule { nftFeatureToggles = nftFeatureToggles, ) } + + @Provides + @Singleton + fun providesUpdateRemoteWalletsInfoUseCase( + walletsRepository: WalletsRepository, + userWalletsSyncDelegate: UserWalletsSyncDelegate, + ): UpdateRemoteWalletsInfoUseCase { + return UpdateRemoteWalletsInfoUseCase( + walletsRepository = walletsRepository, + userWalletsSyncDelegate = userWalletsSyncDelegate, + ) + } + + @Provides + @Singleton + fun providesGetSavedWalletChangesIdUseCase( + userWalletsListManager: UserWalletsListManager, + ): GetSavedWalletChangesUseCase { + return GetSavedWalletChangesUseCase( + userWalletsListManager = userWalletsListManager, + ) + } + + @Provides + @Singleton + fun providesAssociateWalletsWithApplicationIdUseCase( + walletsRepository: WalletsRepository, + ): AssociateWalletsWithApplicationIdUseCase { + return AssociateWalletsWithApplicationIdUseCase( + walletsRepository = walletsRepository, + ) + } + + @Provides + @Singleton + fun providesSetNotificationsEnabledUseCase( + walletsRepository: WalletsRepository, + currenciesRepository: CurrenciesRepository, + ): SetNotificationsEnabledUseCase { + return SetNotificationsEnabledUseCase( + walletsRepository = walletsRepository, + currenciesRepository = currenciesRepository, + ) + } + + @Provides + @Singleton + fun providesGetWalletNotificationsEnabledUseCase( + walletsRepository: WalletsRepository, + ): GetWalletNotificationsEnabledUseCase { + return GetWalletNotificationsEnabledUseCase( + walletsRepository = walletsRepository, + ) + } + + @Provides + @Singleton + fun providesGetIsNotificationsEnabledUseCase( + walletsRepository: WalletsRepository, + ): GetIsNotificationsEnabledUseCase { + return GetIsNotificationsEnabledUseCase( + walletsRepository = walletsRepository, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt index 3bfc0b3a44..a95acf5cd6 100644 --- a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt +++ b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt @@ -1,7 +1,9 @@ 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 @@ -31,4 +33,10 @@ internal object AppRouterModule { @Provides @Singleton fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig() + + @Provides + @Singleton + fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle { + return RoutingFeatureToggle(featureTogglesManager) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 63058e7738..b812453401 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -5,6 +5,7 @@ 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.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store @@ -34,7 +35,7 @@ class TapWalletManager( } private suspend fun loadUserWalletData(userWallet: UserWallet) { - Analytics.setContext(userWallet.scanResponse) + Analytics.setContext(userWallet.requireColdWallet().scanResponse) // [REDACTED_TASK_KEY] val scanResponse = userWallet.scanResponse tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index 9b04ab7d07..4c6e048c73 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.card import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve @@ -11,15 +10,16 @@ import com.tangem.common.doOnSuccess import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.data.common.currency.getNetwork +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.BackendId 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.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter @@ -33,7 +33,7 @@ private typealias DerivedKeys = Map internal class DefaultDerivationsRepository( private val tangemSdkManager: TangemSdkManager, private val userWalletsStore: UserWalletsStore, - private val excludedBlockchains: ExcludedBlockchains, + private val networkFactory: NetworkFactory, private val dispatchers: CoroutineDispatcherProvider, ) : DerivationsRepository { @@ -41,17 +41,16 @@ internal class DefaultDerivationsRepository( derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) } - override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) { + override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") derivePublicKeysByNetworks( userWalletId = userWalletId, networks = networkIds.mapNotNull { - getNetwork( + networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, - scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, + scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] ) }, ) @@ -62,6 +61,8 @@ internal class DefaultDerivationsRepository( userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") } + userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] + if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) { Timber.d("Nothing to derive") return @@ -83,17 +84,17 @@ internal class DefaultDerivationsRepository( ): Boolean { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") - val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse) - .findByNetworks( - networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> - getNetwork( - blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, - extraDerivationPath = extraDerivationPath, - scanResponse = userWallet.scanResponse, - excludedBlockchains = excludedBlockchains, - ) - }, - ) + val derivations = + MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] + .findByNetworks( + networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> + networkFactory.create( + blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, + extraDerivationPath = extraDerivationPath, + scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + ) + }, + ) return derivations.isNotEmpty() } @@ -108,7 +109,8 @@ internal class DefaultDerivationsRepository( ).doOnSuccess { response -> updatePublicKeys(userWalletId = userWalletId, keys = response.entries) .doOnSuccess { - validateDerivations(scanResponse = it.scanResponse, derivations = derivations) + // TODO [REDACTED_TASK_KEY] + validateDerivations(scanResponse = it.requireColdWallet().scanResponse, derivations = derivations) return response.entries } .doOnFailure { throw it } @@ -138,12 +140,12 @@ internal class DefaultDerivationsRepository( return withContext(dispatchers.io) { userWalletsStore.update( userWalletId = userWalletId, - update = { userWallet -> userWallet.updateDerivedKeys(keys) }, + update = { userWallet -> userWallet.requireColdWallet().updateDerivedKeys(keys) }, // TODO [REDACTED_TASK_KEY] ) } } - private fun UserWallet.updateDerivedKeys(keys: DerivedKeys): UserWallet { + private fun UserWallet.Cold.updateDerivedKeys(keys: DerivedKeys): UserWallet { return copy( scanResponse = scanResponse.copy( derivedKeys = getUpdatedDerivedKeys(oldKeys = scanResponse.derivedKeys, newKeys = keys), diff --git a/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt index af980fd382..9bbb08d575 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt @@ -2,16 +2,17 @@ package com.tangem.tap.domain.card import com.tangem.blockchain.blockchains.cardano.CardanoUtils import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.configs.CardConfig import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network import com.tangem.domain.models.scan.KeyWalletPublicKey import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network import com.tangem.operations.derivation.ExtendedPublicKeysMap private typealias DerivationData = Pair> @@ -49,7 +50,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { private fun List.mapToNewDerivations(): List { val config = CardConfig.createConfig(scanResponse.card) return mapNotNull { network -> - val blockchain = Blockchain.fromId(id = network.id.value) + val blockchain = network.toBlockchain() val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null findNewDerivations(curve = curve, scanResponse = scanResponse, network = network) @@ -74,7 +75,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { } private fun Network.getDerivationCandidates(curve: EllipticCurve): List { - val blockchain = Blockchain.fromId(id = this.id.value) + val blockchain = this.toBlockchain() return buildList { add(blockchain.getDerivationPath(curve = curve)) diff --git a/app/src/main/java/com/tangem/tap/domain/notifications/DefaultNotificationsFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/notifications/DefaultNotificationsFeatureToggles.kt new file mode 100644 index 0000000000..5a58923bb9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/notifications/DefaultNotificationsFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.domain.notifications + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles + +internal class DefaultNotificationsFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : NotificationsFeatureToggles { + override val isNotificationsEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("PUSH_NOTIFICATIONS_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 1fa9dc2b67..758979cd91 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.tasks.visa import arrow.core.Either import arrow.core.getOrElse -import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.blockchain.common.UnmarshalHelper import com.tangem.common.CompletionResult @@ -14,22 +13,18 @@ import com.tangem.common.extensions.toHexString import com.tangem.common.map import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource import com.tangem.core.error.ext.tangemError -import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.VisaOtpData import com.tangem.datasource.local.visa.hasSavedOTP -import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.error.VisaActivationError -import com.tangem.domain.visa.error.VisaAuthorizationAPIError import com.tangem.domain.visa.model.* import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.operations.GenerateOTPCommand import com.tangem.operations.attestation.AttestCardKeyCommand -import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.sign.SignHashCommand import com.tangem.operations.sign.SignHashResponse @@ -95,23 +90,7 @@ class VisaCardActivationTask @AssistedInject constructor( context.signAuthorizationChallenge(mode.authorizationChallenge) } is VisaCardActivationTaskMode.SignOnly -> { - val wallet = - card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } - ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - - val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) { - is CompletionResult.Failure -> { - return CompletionResult.Failure(deriveKeyResult.error) - } - is CompletionResult.Success -> { - deriveKeyResult.data - } - } - - context.signData( - mode.dataToSignByCardWallet, - derivedPublicKey, - ) + context.signData(mode.dataToSignByCardWallet) } } } @@ -164,16 +143,7 @@ class VisaCardActivationTask @AssistedInject constructor( card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) { - is CompletionResult.Failure -> { - return CompletionResult.Failure(deriveKeyResult.error) - } - is CompletionResult.Success -> { - deriveKeyResult.data - } - } - - val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey) + val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey) .getOrElse { return CompletionResult.Failure(it.tangemError) } .value @@ -190,10 +160,7 @@ class VisaCardActivationTask @AssistedInject constructor( otpTaskDeferred.await() - signData( - dataToSign = dataToSign, - derivedPublicKey = derivedPublicKey, - ) + signData(dataToSign = dataToSign) } } @@ -201,28 +168,24 @@ class VisaCardActivationTask @AssistedInject constructor( signedChallenge: VisaAuthSignedChallenge, cardWalletAddress: String, ): Either = either { - catch( - block = { - val tokens = visaAuthRepository.getAccessTokens(signedChallenge) + val tokens = visaAuthRepository.getAccessTokens(signedChallenge) + .getOrElse { raise(it.tangemError) } - visaAuthTokenStorage.store(cardId, tokens) + visaAuthTokenStorage.store(cardId, tokens) - val remoteState = visaActivationRepository.getActivationRemoteState() - if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) { - raise(VisaActivationError.WrongRemoteState.tangemError) - } + val remoteState = visaActivationRepository.getActivationRemoteState() + .getOrElse { raise(it.tangemError) } - visaActivationRepository.getCardWalletAcceptanceData( - VisaCardWalletDataToSignRequest( - activationOrderInfo = remoteState.activationOrderInfo, - cardWalletAddress = cardWalletAddress, - ), - ) - }, - catch = { - raise(VisaAuthorizationAPIError.tangemError) - }, - ) + if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) { + return raise(VisaActivationError.WrongRemoteState.tangemError) + } + + visaActivationRepository.getCardWalletAcceptanceData( + VisaCardWalletDataToSignRequest( + activationOrderInfo = remoteState.activationOrderInfo, + cardWalletAddress = cardWalletAddress, + ), + ).getOrElse { raise(it.tangemError) } } private suspend fun SessionContext.createWallet(): CompletionResult { @@ -294,7 +257,6 @@ class VisaCardActivationTask @AssistedInject constructor( private suspend fun SessionContext.signData( dataToSign: VisaDataToSignByCardWallet, - derivedPublicKey: ExtendedPublicKey, ): CompletionResult { val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) @@ -306,7 +268,6 @@ class VisaCardActivationTask @AssistedInject constructor( val task = SignHashCommand( hash = dataToSign.hashToSign.hexToBytes(), walletPublicKey = wallet.publicKey, - derivationPath = VisaUtilities.visaDefaultDerivationPath, ) val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { @@ -325,7 +286,7 @@ class VisaCardActivationTask @AssistedInject constructor( handleSignedData( dataToSign = dataToSign, response = result.data, - derivedPublicKey = derivedPublicKey, + walletPublicKey = wallet.publicKey, ) } is CompletionResult.Failure -> { @@ -335,23 +296,9 @@ class VisaCardActivationTask @AssistedInject constructor( } } - private suspend fun SessionContext.deriveKey(publicKey: ByteArray): CompletionResult { - val derivationPath = VisaUtilities.visaDefaultDerivationPath - ?: return CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError) - - val derivationTask = DeriveWalletPublicKeyTask(publicKey, derivationPath) - val derivationTaskResult = suspendCancellableCoroutine { continuation -> - derivationTask.run(session) { result -> - continuation.resume(result) - } - } - - return derivationTaskResult - } - private suspend fun SessionContext.handleSignedData( dataToSign: VisaDataToSignByCardWallet, - derivedPublicKey: ExtendedPublicKey, + walletPublicKey: ByteArray, response: SignHashResponse, ): CompletionResult { val otp = otpStorage.getOTP(cardId) ?: run { @@ -362,7 +309,7 @@ class VisaCardActivationTask @AssistedInject constructor( val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended( signature = response.signature, hash = dataToSign.hashToSign.hexToBytes(), - publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(), + publicKey = walletPublicKey.toDecompressedPublicKey(), ).asRSVLegacyEVM().toHexString().lowercase() val signedActivationData = dataToSign.sign( diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 5b892c93ed..dc7b40ea9b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -40,17 +40,12 @@ class VisaCustomerWalletApproveTask( } if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) { - // TODO TVF-21 - callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve"))) + callback(CompletionResult.Failure(VisaActivationError.VisaCardForApproval.tangemError)) return } if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) { - callback( - CompletionResult.Failure( - TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), // TODO TVF-21 - ), - ) + callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError)) return } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index adc8e80741..8922e9b2e7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -7,12 +7,6 @@ internal class DefaultTokensFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, ) : TokensFeatureToggles { - override val isNetworksLoadingRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED") - - override val isQuotesLoadingRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED") - override val isStakingLoadingRefactoringEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index fe02ce769f..2d52d344fe 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -6,6 +6,8 @@ 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 @@ -206,6 +208,7 @@ internal class BiometricUserWalletsListManager( changeSelectedUserWallet: Boolean, canOverridePublicInfo: Boolean, ): CompletionResult { + userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] val encryptionKey = userWallet.scanResponse.card.encryptionKey ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } ?: return CompletionResult.Success(Unit) // No encryption key, no need to save diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 8dca07bec0..df0951fab2 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -6,28 +6,34 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation - get() = UserWalletSensitiveInformation( - wallets = scanResponse.card.wallets, - visaCardActivationStatus = scanResponse.visaCardActivationStatus, - ) + get() = when (this) { + is UserWallet.Cold -> UserWalletSensitiveInformation( + wallets = scanResponse.card.wallets, + visaCardActivationStatus = scanResponse.visaCardActivationStatus, + ) + is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") + } internal val UserWallet.publicInformation: UserWalletPublicInformation - get() = UserWalletPublicInformation( - name = name, - walletId = walletId, - cardsInWallet = cardsInWallet, - isMultiCurrency = isMultiCurrency, - scanResponse = scanResponse.copy( - card = scanResponse.card.copy( - wallets = emptyList(), + get() = when (this) { + is UserWallet.Cold -> UserWalletPublicInformation( + name = name, + walletId = walletId, + cardsInWallet = cardsInWallet, + isMultiCurrency = isMultiCurrency, + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = emptyList(), + ), + visaCardActivationStatus = null, ), - visaCardActivationStatus = null, - ), - hasBackupError = hasBackupError, - ) + hasBackupError = hasBackupError, + ) + is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") + } internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { - return UserWallet( + return UserWallet.Cold( name = name, walletId = walletId, cardsInWallet = cardsInWallet, @@ -42,14 +48,19 @@ internal fun List.toUserWallets(): List } internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInformation): UserWallet { - return copy( - scanResponse = scanResponse.copy( - card = scanResponse.card.copy( - wallets = sensitiveInformation.wallets, - ), - visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, - ), - ) + return when (this) { + is UserWallet.Cold -> { + copy( + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = sensitiveInformation.wallets, + ), + visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, + ), + ) + } + is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") + } } internal fun List.updateWith( @@ -68,11 +79,16 @@ internal fun List.updateWith( internal fun List.lockAll(): List = map(UserWallet::lock) -internal fun UserWallet.lock(): UserWallet = copy( - scanResponse = scanResponse.copy( - card = scanResponse.card.copy( - wallets = emptyList(), - ), - visaCardActivationStatus = null, - ), -) \ No newline at end of file +internal fun UserWallet.lock(): UserWallet = when (this) { + is UserWallet.Cold -> { + copy( + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = emptyList(), + ), + visaCardActivationStatus = null, + ), + ) + } + is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index b83d23f8d9..46a8dadc5e 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -2,29 +2,24 @@ package com.tangem.tap.domain.visa import arrow.core.getOrElse import com.tangem.common.CompletionResult -import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.datasource.local.visa.VisaAuthTokenStorage -import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.visa.error.VisaActivationError -import com.tangem.domain.visa.error.VisaAuthorizationAPIError +import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.error.VisaCardScanError import com.tangem.domain.visa.model.* import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.operations.attestation.AttestCardKeyCommand import com.tangem.operations.attestation.AttestCardKeyResponse -import com.tangem.operations.derivation.DeriveWalletPublicKeyTask -import com.tangem.operations.sign.SignHashCommand -import com.tangem.operations.sign.SignHashResponse +import com.tangem.operations.attestation.AttestWalletKeyResponse +import com.tangem.operations.attestation.AttestWalletKeyTask import kotlinx.coroutines.suspendCancellableCoroutine import timber.log.Timber import javax.inject.Inject @@ -61,54 +56,19 @@ internal class VisaCardScanHandler @Inject constructor( session = session, ) - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { + card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { val activationInput = VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet) val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput) return CompletionResult.Success(activationStatus) } - return context.deriveKey(wallet) - } - - private suspend fun SessionContext.deriveKey(wallet: CardWallet): CompletionResult { - val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run { - Timber.e("Failed to create derivation path while first scan") - - return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError) - } - - val derivationTask = DeriveWalletPublicKeyTask(wallet.publicKey, derivationPath) - val derivationTaskResult = suspendCancellableCoroutine { continuation -> - derivationTask.run(session) { result -> - continuation.resume(result) - } - } - return handleDerivationResponse(derivationTaskResult) - } - - private suspend fun SessionContext.handleDerivationResponse( - result: CompletionResult, - ): CompletionResult { - return when (result) { - is CompletionResult.Success -> { - Timber.i("Start task for loading challenge for Visa wallet") - handleWalletAuthorization() - } - is CompletionResult.Failure -> { - CompletionResult.Failure(result.error) - } - } + return context.handleWalletAuthorization() } private suspend fun SessionContext.handleWalletAuthorization(): CompletionResult { Timber.i("Started handling authorization using Visa wallet") - val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run { - Timber.e("Failed to create derivation path while handling wallet authorization") - return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError) - } - val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { @@ -116,43 +76,35 @@ internal class VisaCardScanHandler @Inject constructor( return CompletionResult.Failure(VisaCardScanError.FailedToFindWallet.tangemError) } - val extendedPublicKey = wallet.derivedKeys[derivationPath] ?: run { - Timber.e("Failed to find extended public key while handling wallet authorization") - return CompletionResult.Failure(VisaCardScanError.FailedToFindDerivedWalletKey.tangemError) - } - - val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey) + val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey) .getOrElse { return CompletionResult.Failure(it.tangemError) } Timber.i("Requesting challenge for wallet authorization") - val challengeResponse = runCatching { - // TODO [REDACTED_TASK_KEY] - error("sign and get specific error to switch to card_id flow") - visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value) - }.getOrElse { - Timber.i( - "Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key", - ) - return handleCardAuthorization( - cardWalletAddress = walletAddress.value, - ) - } + + val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value) + .getOrElse { + Timber.i("Failed to get Access token for Wallet public key authorization") + return CompletionResult.Failure(it.tangemError) + } val signChallengeResult = signChallengeWithWallet( publicKey = wallet.publicKey, - derivationPath = derivationPath, nonce = challengeResponse.challenge, ) return when (signChallengeResult) { is CompletionResult.Success -> { + val signature = signChallengeResult.data.cardSignature ?: run { + Timber.i("Failed to sign challenge with Wallet public key") + return CompletionResult.Failure(VisaCardScanError.FailedToSignChallenge.tangemError) + } + Timber.i("Challenge signed with Wallet public key") handleWalletAuthorizationTokens( cardWalletAddress = walletAddress.value, - signedChallenge = challengeResponse - .toSignedChallenge(signChallengeResult.data.signature.toHexString()), + signedChallenge = challengeResponse.toSignedChallenge(signature.toHexString()), ) } is CompletionResult.Failure -> { @@ -166,16 +118,19 @@ internal class VisaCardScanHandler @Inject constructor( cardWalletAddress: String, signedChallenge: VisaAuthSignedChallenge, ): CompletionResult { - val authorizationTokensResponse = runCatching { - visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge) - }.getOrElse { - Timber.i( - "Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key", - ) - return handleCardAuthorization( - cardWalletAddress = cardWalletAddress, - ) - } + val authorizationTokensResponse = visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge) + .getOrElse { + Timber.i("Failed to get Access token for Wallet public key authorization.") + return if ( + it is VisaApiError.ProductInstanceIsNotActivated || + it is VisaApiError.ProductInstanceNotFoundActivationRequired + ) { + Timber.i("Proceeding with card authorization.") + handleCardAuthorization(cardWalletAddress = cardWalletAddress) + } else { + CompletionResult.Failure(it.tangemError) + } + } Timber.i("Authorized using Wallet public key successfully") @@ -190,14 +145,12 @@ internal class VisaCardScanHandler @Inject constructor( Timber.i("Requesting authorization challenge to sign") - val challengeResponse = runCatching { - visaAuthRepository.getCardAuthChallenge( - cardId = card.cardId, - cardPublicKey = card.cardPublicKey.toHexString(), - ) - }.getOrElse { - Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.message}") - return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError) + val challengeResponse = visaAuthRepository.getCardAuthChallenge( + cardId = card.cardId, + cardPublicKey = card.cardPublicKey.toHexString(), + ).getOrElse { + Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.errorCode}") + return CompletionResult.Failure(it.tangemError) } Timber.i("Received challenge to sign: ${challengeResponse.challenge}") @@ -217,17 +170,14 @@ internal class VisaCardScanHandler @Inject constructor( } } - @Suppress("UnusedPrivateMember") - val authorizationTokensResponse = runCatching { - visaAuthRepository.getAccessTokens( - signedChallenge = challengeResponse.toSignedChallenge( - signedChallenge = attestCardKeyResponse.cardSignature.toHexString(), - salt = attestCardKeyResponse.salt.toHexString(), - ), - ) - }.getOrElse { - Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}") - return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError) + val authorizationTokensResponse = visaAuthRepository.getAccessTokens( + signedChallenge = challengeResponse.toSignedChallenge( + signedChallenge = attestCardKeyResponse.cardSignature.toHexString(), + salt = attestCardKeyResponse.salt.toHexString(), + ), + ).getOrElse { + Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}") + return CompletionResult.Failure(it.tangemError) } visaAuthTokenStorage.store( @@ -235,11 +185,9 @@ internal class VisaCardScanHandler @Inject constructor( tokens = authorizationTokensResponse, ) - val activationRemoteState = runCatching { - visaActivationRepository.getActivationRemoteState() - }.getOrElse { - Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}") - return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError) + val activationRemoteState = visaActivationRepository.getActivationRemoteState().getOrElse { + Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}") + return CompletionResult.Failure(it.tangemError) } val error = when (activationRemoteState) { @@ -271,13 +219,11 @@ internal class VisaCardScanHandler @Inject constructor( private suspend fun SessionContext.signChallengeWithWallet( publicKey: ByteArray, - derivationPath: DerivationPath, nonce: String, - ): CompletionResult { - val signHashCommand = SignHashCommand( - hash = nonce.hexToBytes(), - walletPublicKey = publicKey, - derivationPath = derivationPath, + ): CompletionResult { + val signHashCommand = AttestWalletKeyTask( + publicKey = publicKey, + challenge = nonce.hexToBytes(), ) return suspendCancellableCoroutine { signHashCommand.run(session) { result -> diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index ab00fc7cfa..02c60262fc 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -17,6 +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.operations.sign.SignHashCommand import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate @@ -133,7 +134,7 @@ class WalletConnectSdkHelper { fun isDemoCard(): Boolean { val userWallet = userWalletsListManager.selectedUserWalletSync ?: return false - return userWallet.scanResponse.isDemoCard() + return userWallet is UserWallet.Cold && userWallet.scanResponse.isDemoCard() } private suspend fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index a66065157e..2d1295d3b6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -13,6 +13,7 @@ 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 @@ -64,14 +65,18 @@ internal class DefaultLegacyWalletConnectRepositoryFacade constructor( if (isNewWc) stub.updateSessions() else legacy.updateSessions() } - override fun pair(uri: String, source: SourceType) { + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) { val src = when (source) { SourceType.QR -> WcPairRequest.Source.QR SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD SourceType.ETC -> WcPairRequest.Source.ETC } - if (isNewWc) wcPairService.pair(WcPairRequest(uri, src)) else legacy.pair(uri, source) + if (isNewWc) { + wcPairService.pair(WcPairRequest(uri = uri, source = src, userWalletId = userWalletId)) + } else { + legacy.pair(userWalletId = userWalletId, uri = uri, source = source) + } } override fun disconnect(topic: String) { @@ -110,7 +115,7 @@ internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository override fun updateSessions() = Unit - override fun pair(uri: String, source: SourceType) = Unit + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) = Unit override fun disconnect(topic: String) = Unit @@ -405,7 +410,7 @@ internal class DefaultLegacyWalletConnectRepository( this.userNamespaces = userNamespaces } - override fun pair(uri: String, source: SourceType) { + override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) { analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source)) WalletKit.pair( params = Wallet.Params.Pair(uri), diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index 59d5954e00..c0e22a3b7d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -1,6 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain 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 @@ -19,7 +20,7 @@ interface LegacyWalletConnectRepository { fun updateSessions() - fun pair(uri: String, source: SourceType) + fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) fun disconnect(topic: String) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index d8d5a989ae..0487bee8ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,10 +1,10 @@ package com.tangem.tap.domain.walletconnect2.domain import arrow.core.flatten -import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.Network +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.model.legacy.Session @@ -12,6 +12,9 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit 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.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.filterNotNull @@ -139,7 +142,11 @@ class WalletConnectInteractor( if (deeplinkStack.empty()) return val lastDeeplink = deeplinkStack.pop() val action = WalletConnectAction - .OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + .OpenSession( + wcUri = lastDeeplink, + source = WalletConnectAction.OpenSession.SourceType.DEEPLINK, + userWalletId = UserWalletId(userWalletId), + ) store.dispatchOnMain(action) }.onFailure { Timber.e("WC deeplink handling failed. $it") @@ -393,7 +400,11 @@ class WalletConnectInteractor( } if (isWalletConnectReadyForDeepLinks) { - val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK) + val action = WalletConnectAction.OpenSession( + wcUri = deeplink, + source = WalletConnectAction.OpenSession.SourceType.DEEPLINK, + userWalletId = UserWalletId(userWalletId), + ) store.dispatchOnMain(action) } else { deeplinkStack.push(deeplink) @@ -401,6 +412,7 @@ class WalletConnectInteractor( } private fun getCardId(userWallet: UserWallet): String? { + userWallet.requireColdWallet() // [REDACTED_TASK_KEY] return if (userWallet.scanResponse.card.backupStatus?.isActive != true) { userWallet.cardId } else { // if wallet has backup, any card from wallet can be used to sign @@ -410,7 +422,8 @@ class WalletConnectInteractor( private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List): List { val walletManagers = networks.mapNotNull { - val blockchain = Blockchain.fromId(it.id.value) + val blockchain = it.toBlockchain() + walletManagersFacade.getOrCreateWalletManager( userWalletId = userWallet.walletId, blockchain = blockchain, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 7391c77f5b..4288f711b9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect +import com.tangem.domain.wallets.models.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 @@ -14,6 +15,7 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, val source: SourceType, + val userWalletId: UserWalletId, ) : WalletConnectAction() { enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 6bbd548de4..7439241a50 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -63,7 +63,11 @@ class WalletConnectMiddleware { is WalletConnectAction.OpenSession -> { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { - '2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source) + '2' -> walletConnectRepository.pair( + uri = action.wcUri, + source = action.source, + userWalletId = action.userWalletId, + ) '1' -> { store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest) store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 1390b8d0bd..3ae653038c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -19,6 +19,7 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse 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 @@ -86,7 +87,8 @@ internal class CardSettingsModel @Inject constructor( val userWallet = getUserWalletUseCase(userWalletId) .getOrElse { error("User wallet $userWalletId not found") } - cardSdkConfigRepository.isBiometricsRequestPolicy = userWallet.scanResponse.card.isAccessCodeSet && + cardSdkConfigRepository.isBiometricsRequestPolicy = + userWallet.requireColdWallet().scanResponse.card.isAccessCodeSet && // TODO [REDACTED_TASK_KEY] settingsRepository.shouldSaveAccessCodes() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index f401bbfc09..34aa6d687a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -14,6 +14,7 @@ import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.common.util.cardTypesResolver 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 @@ -59,6 +60,7 @@ internal class ResetCardModel @Inject constructor( // Use only for card-specific data private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId) .getOrElse { error("Failed to get user wallet: $it") } + .requireColdWallet() private val currentCardTypesResolver = userWallet.cardTypesResolver private val currentUserCodeParams = ResetCardUserCodeParams( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt index 73a0989519..0974e0db1b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/DefaultWalletConnectComponent.kt @@ -22,10 +22,10 @@ import org.rekotlin.StoreSubscriber @Suppress("UnusedPrivateMember") internal class DefaultWalletConnectComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - @Assisted params: Unit, + @Assisted params: WalletConnectComponent.Params, ) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber { - private val model: WalletConnectModel = getOrCreateModel() + private val model: WalletConnectModel = getOrCreateModel(params) private var screenState: MutableState = mutableStateOf(model.updateState(store.state.walletConnectState)) @@ -65,6 +65,9 @@ internal class DefaultWalletConnectComponent @AssistedInject constructor( @AssistedFactory interface Factory : WalletConnectComponent.Factory { - override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent + override fun create( + context: AppComponentContext, + params: WalletConnectComponent.Params, + ): DefaultWalletConnectComponent } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt index 21deae59a7..762b7afb71 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt @@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable 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.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList @@ -22,13 +24,22 @@ import javax.inject.Inject internal class WalletConnectModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + paramsContainer: ParamsContainer, ) : Model() { + private val params = paramsContainer.require() + init { modelScope.launch { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) } + .map { + WalletConnectAction.OpenSession( + wcUri = it, + source = WalletConnectAction.OpenSession.SourceType.QR, + userWalletId = params.userWalletId, + ) + } .collect { store.dispatch(it) } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt index 4e1f4ab3fb..6b9d8c4330 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt @@ -2,7 +2,9 @@ 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 interface WalletConnectComponent : ComposableContentComponent { - interface Factory : ComponentFactory + data class Params(val userWalletId: UserWalletId) + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt index a3b184dc8e..132cbd1773 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt @@ -20,7 +20,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.tokens.TokensAction -import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.IntroductionProcess @@ -53,7 +53,7 @@ internal class HomeModel @Inject constructor( private val settingsRepository: SettingsRepository, private val urlOpener: UrlOpener, private val analyticsEventHandler: AnalyticsEventHandler, - private val userWalletBuilderFactory: UserWalletBuilder.Factory, + private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, getUserCountryUseCase: GetUserCountryUseCase, ) : Model() { @@ -117,7 +117,7 @@ internal class HomeModel @Inject constructor( } private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { - val userWallet = userWalletBuilderFactory.create(scanResponse = scanResponse).build() + val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() if (userWallet == null) { Timber.e("User wallet not created") diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index f43a2d6aa6..d020b24f18 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -100,7 +100,7 @@ private suspend fun readCard() { } private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { - val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse) + val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse) val userWallet = userWalletBuilder.build().guard { Timber.e("User wallet not created") diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index c00ae2518f..29b1eb08c8 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -4,6 +4,7 @@ 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 @@ -22,6 +23,10 @@ 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.notifications.GetApplicationIdUseCase +import com.tangem.domain.notifications.SendPushTokenUseCase +import com.tangem.domain.notifications.models.ApplicationId +import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.onramp.FetchHotCryptoUseCase import com.tangem.domain.promo.GetStoryContentUseCase @@ -31,6 +36,10 @@ 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.GetSavedWalletChangesUseCase +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 @@ -38,11 +47,15 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.wallet.BuildConfig import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @Suppress("LongParameterList") @HiltViewModel @@ -56,7 +69,6 @@ internal class MainViewModel @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val dispatchers: CoroutineDispatcherProvider, private val fetchStakingTokensUseCase: FetchStakingTokensUseCase, - private val apiConfigsManager: ApiConfigsManager, private val fetchUserCountryUseCase: FetchUserCountryUseCase, @GlobalUiMessageSender private val messageSender: UiMessageSender, private val keyboardValidator: KeyboardValidator, @@ -67,6 +79,14 @@ internal class MainViewModel @Inject constructor( private val onboardingRepository: OnboardingRepository, private val deepLinksRegistry: DeepLinksRegistry, private val onrampDeepLinkFactory: OnrampDeepLink.Factory, + private val notificationsToggles: NotificationsFeatureToggles, + private val getApplicationIdUseCase: GetApplicationIdUseCase, + private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase, + private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase, + private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase, + private val sendPushTokenUseCase: SendPushTokenUseCase, + private val apiConfigsManager: ApiConfigsManager, + routingFeatureToggle: RoutingFeatureToggle, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel() { @@ -77,32 +97,38 @@ internal class MainViewModel @Inject constructor( private set init { + /** + * Run any data initialization here that needs to happen before the app starts + * and is hidden behind the SplashScreen + */ loadApplicationResources() - viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } + /** Run any API data load here that runs in parallel and does not block the app from starting */ + launchAPIRequests { + launch { fetchHotCryptoUseCase() } - viewModelScope.launch { - fetchUserCountryUseCase().onLeft { - Timber.e("Unable to fetch the user country code $it") - } + launch { fetchAppCurrenciesUseCase() } + + launch { fetchStakingTokens() } + + launch { initPushNotifications() } } - viewModelScope.launch { fetchHotCryptoUseCase() } + viewModelScope.launch { incrementAppLaunchCounterUseCase() } - updateAppCurrencies() observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() - fetchStakingTokens() - deleteDeprecatedLogsUseCase() sendKeyboardIdentifierEvent() preloadImages() - initializeDeepLinks() + if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { + initializeDeepLinks() + } } fun checkForUnfinishedBackup() { @@ -114,38 +140,55 @@ internal class MainViewModel @Inject constructor( /** Loading the resources needed to run the application */ private fun loadApplicationResources() { - viewModelScope.launch(dispatchers.main) { - apiConfigsManager.initialize() + viewModelScope.launch { + launchAPIRequests { + launch { blockchainSDKFactory.init() } + + launch { + withTimeout(timeMillis = 1.seconds.inWholeMilliseconds) { fetchUserCountry() } + } + } - blockchainSDKFactory.init() prepareSelectedWalletFeedback() isSplashScreenShown = false } } + private suspend fun fetchUserCountry() { + fetchUserCountryUseCase().onLeft { + Timber.e("Unable to fetch the user country code $it") + } + } + + private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) { + viewModelScope.launch { + if (BuildConfig.TESTER_MENU_ENABLED) { + apiConfigsManager.isInitialized + .filter { it } + .first() // wait until isInitialized becomes true + + function() + } else { + function() + } + } + } + private fun prepareSelectedWalletFeedback() { userWalletsListManager.selectedUserWallet .distinctUntilChanged() .onEach { userWallet -> - Analytics.setContext(userWallet.scanResponse) + Analytics.setContext(userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] } .flowOn(dispatchers.io) .launchIn(viewModelScope) } - private fun updateAppCurrencies() { - viewModelScope.launch(dispatchers.main) { - fetchAppCurrenciesUseCase.invoke() - } - } - - private fun fetchStakingTokens() { - viewModelScope.launch(dispatchers.main) { - fetchStakingTokensUseCase() - .onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") } - .onRight { Timber.d("Staking token list was fetched successfully") } - } + private suspend fun fetchStakingTokens() { + fetchStakingTokensUseCase() + .onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") } + .onRight { Timber.d("Staking token list was fetched successfully") } } private fun observeFlips() { @@ -343,4 +386,20 @@ internal class MainViewModel @Inject constructor( private fun initializeDeepLinks() { deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope)) } + + private suspend fun initPushNotifications() { + if (notificationsToggles.isNotificationsEnabled) { + getApplicationIdUseCase().onRight { applicationId -> + sendPushTokenUseCase(applicationId = applicationId) + associateWalletsWithApplicationId(applicationId = applicationId) + updateRemoteWalletsInfoUseCase(applicationId = applicationId) + }.onLeft { Timber.e(it.toString()) } + } + } + + private fun associateWalletsWithApplicationId(applicationId: ApplicationId) { + subscribeOnWalletsUseCase().onEach { wallets -> + associateWalletsWithApplicationIdUseCase(applicationId, wallets) + }.launchIn(viewModelScope) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 9bda54aa7d..2e92c8c5f4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -1,15 +1,16 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.common.Blockchain +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.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.NetworkAddress 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 @@ -79,7 +80,7 @@ object TradeCryptoMiddleware { val status = action.cryptoCurrencyStatus val currency = status.currency - val blockchain = Blockchain.fromId(currency.network.id.value) + val blockchain = currency.network.toBlockchain() val exchangeManager = store.state.globalState.exchangeManager val topUrl = exchangeManager.getUrl( action = CurrencyExchangeManager.Action.Buy, @@ -104,7 +105,7 @@ object TradeCryptoMiddleware { } buyErc20TestnetTokens( - card = action.userWallet.scanResponse.card, + card = action.userWallet.requireColdWallet().scanResponse.card, // TODO [REDACTED_TASK_KEY] walletManager = walletManager, destinationAddress = currency.contractAddress, ) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 26275a69eb..ef31519427 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -15,6 +15,7 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse 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.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* @@ -94,7 +95,7 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> sendSignedInAnalyticsEvent( - scanResponse = selectedUserWallet.scanResponse, + scanResponse = selectedUserWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] signInType = Basic.SignedIn.SignInType.Biometric, ) @@ -117,7 +118,7 @@ internal class WelcomeMiddleware { ) scanCardInternal { scanResponse -> - val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse) + val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse) val userWallet = userWalletBuilder.build() ?: return@scanCardInternal diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt index c39150c2c2..2820d0ed44 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt @@ -3,14 +3,24 @@ package com.tangem.tap.network.auth import com.tangem.common.extensions.toHexString import com.tangem.datasource.api.common.AuthProvider 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?.scanResponse?.card?.cardPublicKey?.toHexString() ?: "" + return userWalletsListManager.selectedUserWalletSync + ?.requireColdWallet()?.scanResponse?.card?.cardPublicKey?.toHexString() ?: "" } override fun getCardId(): String { - return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: "" + return userWalletsListManager.selectedUserWalletSync + ?.requireColdWallet()?.scanResponse?.card?.cardId ?: "" + } + + override fun getCardsPublicKeys(): Map { + return userWalletsListManager.userWalletsSync.filterIsInstance().associate { + it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt index 8cf25d7f82..e69337350b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt @@ -3,8 +3,10 @@ package com.tangem.tap.network.exchangeServices import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.domain.tokens.model.CryptoCurrency +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,6 +27,7 @@ internal class CryptoCurrencyConverter( extraDerivationPath = value.derivationPath, scanResponse = requireNotNull( store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync + ?.requireColdWallet() // TODO [REDACTED_TASK_KEY] ?.scanResponse, ), ), @@ -36,6 +39,7 @@ internal class CryptoCurrencyConverter( extraDerivationPath = value.derivationPath, scanResponse = requireNotNull( store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync + ?.requireColdWallet() // TODO [REDACTED_TASK_KEY] ?.scanResponse, ), ), @@ -44,7 +48,7 @@ internal class CryptoCurrencyConverter( } override fun convertBack(value: CryptoCurrency): Currency { - val blockchain = Blockchain.fromId(value.network.id.value) + val blockchain = value.network.toBlockchain() if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain") return when (value) { is CryptoCurrency.Coin -> Currency.Blockchain( diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index b117321609..83fee90f6c 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -4,15 +4,15 @@ import com.tangem.Message import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.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.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction @@ -68,7 +68,7 @@ class CurrencyExchangeManager( walletAddress: String, isDarkTheme: Boolean, ): String? { - val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value) + val blockchain = cryptoCurrency.network.toBlockchain() if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl() val urlBuilder = getExchangeUrlBuilder(action) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 2671d88af8..32256ffa87 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -12,9 +12,9 @@ import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader 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.tokens.GetNetworkCoinStatusUseCase -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 709fc68592..1641baa48f 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -2,8 +2,8 @@ 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.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index b398116c05..16219f4094 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -1,7 +1,7 @@ package com.tangem.tap.network.exchangeServices.mercuryo import android.net.Uri -import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.extensions.calculateSha512 import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result @@ -10,8 +10,8 @@ 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.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService @@ -77,7 +77,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E ): String { if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() - val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value) + val blockchain = cryptoCurrency.network.toBlockchain() val builder = Uri.Builder() .scheme(ExchangeUrlBuilder.SCHEME) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 17cfce8e39..d57da1a257 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -2,7 +2,7 @@ package com.tangem.tap.network.exchangeServices.moonpay import android.net.Uri import android.util.Base64 -import com.tangem.blockchain.common.Blockchain +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 @@ -10,8 +10,8 @@ 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.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService @@ -133,7 +133,7 @@ class MoonPayService( ): String? { if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException() - val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value) + val blockchain = cryptoCurrency.network.toBlockchain() val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null val moonpayCurrency = status?.availableForSell?.firstOrNull { when (cryptoCurrency) { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 9d8ccb0fbb..526d65968d 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -27,7 +27,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.builder.UserWalletBuilder +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 @@ -78,5 +78,5 @@ data class DaggerGraphState( val uiMessageSender: UiMessageSender? = null, val onlineCardVerifier: OnlineCardVerifier? = null, val cardArworksProvider: CardArtworksProvider? = null, - val userWalletBuilderFactory: UserWalletBuilder.Factory? = null, + val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null, ) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 92ac286e88..64d10c0193 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -22,6 +22,7 @@ import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent.Child import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.utils.ChildFactory +import com.tangem.tap.routing.utils.DeepLinkFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -34,6 +35,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val appRouterConfig: AppRouterConfig, private val uiDependencies: UiDependencies, private val wcRoutingComponentFactory: WcRoutingComponent.Factory, + private val deeplinkFactory: DeepLinkFactory, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -60,7 +62,10 @@ internal class DefaultRoutingComponent @AssistedInject constructor( stack.subscribe(lifecycle) { stack -> val stackItems = stack.items.map { it.configuration } + wcRoutingComponent.onAppRouteChange(stack.active.configuration) + deeplinkFactory.checkRoutingReadiness(stack.active.configuration) + if (appRouterConfig.stack != stackItems) { appRouterConfig.stack = stackItems } diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 0f2b7d5303..1c3e20f554 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -12,18 +12,19 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent +import com.tangem.features.markets.tokenlist.MarketsTokenListComponent import com.tangem.features.nft.component.NFTComponent 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.send.v2.api.NFTSendComponent -import com.tangem.features.send.v2.api.SendFeatureToggles +import com.tangem.features.send.v2.api.SendComponent 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 +import com.tangem.features.walletconnect.components.WalletConnectEntryComponent import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent @@ -47,6 +48,7 @@ internal class ChildFactory @Inject constructor( private val disclaimerComponentFactory: DisclaimerComponent.Factory, private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, + private val marketsTokenListComponentFactory: MarketsTokenListComponent.Factory, private val onrampComponentFactory: OnrampComponent.Factory, private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, @@ -55,7 +57,6 @@ internal class ChildFactory @Inject constructor( private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val welcomeComponentFactory: WelcomeComponent.Factory, private val storiesComponentFactory: StoriesComponent.Factory, - private val sendComponentFactory: SendComponent.Factory, private val stakingComponentFactory: StakingComponent.Factory, private val swapComponentFactory: SwapComponent.Factory, private val homeComponentFactory: HomeComponent.Factory, @@ -71,9 +72,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 redesignedWalletConnectComponentFactory: RedesignedWalletConnectComponent.Factory, + private val sendComponentFactoryV2: SendComponent.Factory, + private val redesignedWalletConnectComponentFactory: WalletConnectEntryComponent.Factory, private val nftComponentFactory: NFTComponent.Factory, private val nftSendComponentFactory: NFTSendComponent.Factory, private val testerRouter: TesterRouter, @@ -252,33 +252,18 @@ internal class ChildFactory @Inject constructor( ) } is AppRoute.Send -> { - if (sendFeatureToggles.isSendV2Enabled) { - createComponentChild( - context = context, - 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( - context = context, - params = SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactory, - ) - } + createComponentChild( + context = context, + params = SendComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + transactionId = route.transactionId, + amount = route.amount, + tag = route.tag, + destinationAddress = route.destinationAddress, + ), + componentFactory = sendComponentFactoryV2, + ) } is AppRoute.Home -> { createComponentChild( @@ -291,13 +276,13 @@ internal class ChildFactory @Inject constructor( if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { createComponentChild( context = context, - params = Unit, + params = RedesignedWalletConnectComponent.Params(route.userWalletId), componentFactory = redesignedWalletConnectComponentFactory, ) } else { createComponentChild( context = context, - params = Unit, + params = WalletConnectComponent.Params(route.userWalletId), componentFactory = walletConnectComponentFactory, ) } @@ -406,6 +391,13 @@ internal class ChildFactory @Inject constructor( componentFactory = nftSendComponentFactory, ) } + is AppRoute.Markets -> { + createComponentChild( + context = context, + params = Unit, + componentFactory = marketsTokenListComponentFactory, + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt new file mode 100644 index 0000000000..f33be6d875 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -0,0 +1,143 @@ +package com.tangem.tap.routing.utils + +import android.net.Uri +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.DeepLinkRoute +import com.tangem.common.routing.DeepLinkScheme +import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler +import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler +import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler +import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler +import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler +import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler +import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler +import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.extensions.uriValidate +import dagger.hilt.android.scopes.ActivityScoped +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.transformLatest +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@ActivityScoped +internal class DeepLinkFactory @Inject constructor( + private val onrampDeepLink: OnrampDeepLinkHandler.Factory, + private val sellDeepLink: SellDeepLinkHandler.Factory, + private val buyDeepLink: BuyDeepLinkHandler.Factory, + private val referralDeepLink: ReferralDeepLinkHandler.Factory, + private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory, + private val walletDeepLink: WalletDeepLinkHandler.Factory, + private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory, + private val stakingDeepLink: StakingDeepLinkHandler.Factory, + private val marketsDeepLink: MarketsDeepLinkHandler.Factory, + private val marketsTokenDetailDeepLink: MarketsTokenDetailDeepLinkHandler.Factory, +) { + private val permittedAppRoute = MutableStateFlow(false) + + private var lastDeepLink: Uri? = null + private val deepLinkHandlerJobHolder = JobHolder() + + @OptIn(ExperimentalCoroutinesApi::class) + fun handleDeeplink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) { + lastDeepLink = deeplinkUri + + Timber.i( + """ + Received deep link intent + |- Received URI: $deeplinkUri + """.trimIndent(), + ) + permittedAppRoute + .transformLatest { isPermitted -> + if (isPermitted) { + lastDeepLink?.let { + launchDeepLink(it, coroutineScope, isFromOnNewIntent) + } + lastDeepLink = null + } + } + .launchIn(coroutineScope) + .saveIn(deepLinkHandlerJobHolder) + } + + /** + * Check if app is ready to handle deeplink + */ + fun checkRoutingReadiness(appRoute: AppRoute) { + permittedAppRoute.value = when (appRoute) { + AppRoute.Initial, + AppRoute.Home, + is AppRoute.Welcome, + is AppRoute.Disclaimer, + is AppRoute.Stories, + is AppRoute.Onboarding, + -> false + else -> true + } + } + + private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) { + when (deeplinkUri.scheme) { + DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope, isFromOnNewIntent) + DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri) + else -> { + Timber.i( + """ + No match found for deep link + |- Received URI: $deeplinkUri + """.trimIndent(), + ) + } + } + } + + private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) { + val queryParams = getQueryParams(deeplinkUri) + when (deeplinkUri.host) { + DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams) + DeepLinkRoute.Sell.host -> sellDeepLink.create(coroutineScope, queryParams) + DeepLinkRoute.Buy.host -> buyDeepLink.create(coroutineScope) + DeepLinkRoute.Referral.host -> referralDeepLink.create() + DeepLinkRoute.Wallet.host -> walletDeepLink.create() + DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create( + coroutineScope = coroutineScope, + queryParams = queryParams, + isFromOnNewIntent = isFromOnNewIntent, + ) + DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams) + DeepLinkRoute.Markets.host -> marketsDeepLink.create() + DeepLinkRoute.MarketTokenDetail.host -> marketsTokenDetailDeepLink.create(coroutineScope, queryParams) + else -> { + Timber.i( + """ + No match found for deep link + |- Received URI: $deeplinkUri + |- With params: $queryParams + """.trimIndent(), + ) + } + } + } + + private fun getQueryParams(uri: Uri): Map { + val params = mutableMapOf() + + uri.queryParameterNames.forEach { paramName -> + val paramValue = uri.getQueryParameter(paramName) + + if (paramName.uriValidate() && paramValue?.uriValidate() == true) { + params[paramName] = paramValue + } + } + + return params + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index 7b9bd90813..3b75f3bc36 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.common.CompletionResult import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.ScanCardException import com.tangem.domain.common.configs.GenericCardConfig @@ -32,11 +33,11 @@ internal class DefaultDerivationsRepositoryTest { tangemSdkManager = tangemSdkManager, userWalletsStore = userWalletsStore, dispatchers = TestingCoroutineDispatcherProvider(), - excludedBlockchains = ExcludedBlockchains(), + networkFactory = NetworkFactory(excludedBlockchains = ExcludedBlockchains()), ) private val defaultUserWalletId = UserWalletId("011") - private val defaultUserWallet = UserWallet( + private val defaultUserWallet = UserWallet.Cold( name = "", walletId = defaultUserWalletId, cardsInWallet = setOf(), diff --git a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt index 209dcbb443..fc6ecdab82 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt @@ -191,7 +191,7 @@ internal class BiometricUserWalletsListManagerTest(private val model: Model) { ) fun createUserWallet(id: String, isLocked: Boolean): UserWallet { - return UserWallet( + return UserWallet.Cold( name = "Wallet $id", walletId = UserWalletId(stringValue = id), cardsInWallet = emptySet(), diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt new file mode 100644 index 0000000000..8078f58b05 --- /dev/null +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -0,0 +1,364 @@ +package com.tangem.tap.routing.utils + +import android.net.Uri +import com.tangem.common.routing.AppRoute +import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler +import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler +import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler +import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler +import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler +import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler +import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler +import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.* +import org.junit.After +import org.junit.Before +import org.junit.Test +import timber.log.Timber + +@OptIn(ExperimentalCoroutinesApi::class) +class DeepLinkFactoryTest { + + private val onrampDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + private val sellDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + private val buyDeepLinkFactory = mockk(relaxed = true) { + every { create(any()) } returns mockk() + } + private val referralDeepLinkFactory = mockk(relaxed = true) { + every { create() } returns mockk() + } + private val walletConnectDeepLinkFactory = mockk(relaxed = true) { + every { create(any()) } returns mockk() + } + private val walletDeepLinkFactory = mockk(relaxed = true) { + every { create() } returns mockk() + } + private val tokenDetailsDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any(), any()) } returns mockk() + } + private val stakingDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + private val marketsDeepLinkFactory = mockk(relaxed = true) { + every { create() } returns mockk() + } + private val marketsTokenDetailDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + + private val mockedUri = mockk(relaxed = true) + private val isFromOnNewIntent: Boolean = false + + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + + private val deepLinkFactory = DeepLinkFactory( + onrampDeepLinkFactory, + sellDeepLinkFactory, + buyDeepLinkFactory, + referralDeepLinkFactory, + walletConnectDeepLinkFactory, + walletDeepLinkFactory, + tokenDetailsDeepLinkFactory, + stakingDeepLinkFactory, + marketsDeepLinkFactory, + marketsTokenDetailDeepLinkFactory, + ) + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setUp() { + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + + Dispatchers.setMain(testDispatcher) + + every { mockedUri.path } returns "/path" + every { mockedUri.toString() } returns "https://example.com/path?query=param" + every { mockedUri.authority } returns "example.com" + every { mockedUri.port } returns 443 // Default HTTPS port + every { mockedUri.fragment } returns null // No fragment in this URI + + Timber.uprootAll() // Disable Timber logging for tests + } + + @OptIn(ExperimentalCoroutinesApi::class) + @After + fun tearDown() { + // Reset the main dispatcher + Dispatchers.resetMain() + // Clean up test coroutines + testScope.cancel() + } + + @Test + fun `handleDeeplink stores uri and launches when permitted`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + // Set permittedAppRoute to true + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + + advanceUntilIdle() + + // Verify onramp handler was called + verify { + onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) + } + } + + @Test + fun `handleDeeplink does not launch when not permitted`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + deepLinkFactory.checkRoutingReadiness(AppRoute.Initial) + + advanceUntilIdle() + + // Verify no handler was called + verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) } + } + + @Test + fun `launchDeepLink handles tangem scheme correctly`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + + advanceUntilIdle() + + verify { + onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) + } + } + + @Test + fun `launchDeepLink handles wc scheme correctly`() = runTest { + every { mockedUri.scheme } returns "wc" + every { mockedUri.host } returns "" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + + advanceUntilIdle() + + verify { + walletConnectDeepLinkFactory.create(eq(mockedUri)) + } + } + + @Test + fun `launchDeepLink ignores unknown scheme`() = runTest { + every { mockedUri.scheme } returns "https" + every { mockedUri.host } returns "example.com" + + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + + advanceUntilIdle() + + verify(inverse = true) { + onrampDeepLinkFactory.create(any(), any()) + sellDeepLinkFactory.create(any(), any()) + buyDeepLinkFactory.create(any()) + referralDeepLinkFactory.create() + walletConnectDeepLinkFactory.create(any()) + walletDeepLinkFactory.create() + tokenDetailsDeepLinkFactory.create(any(), any(), any()) + } + } + + @Test + fun `handleTangemDeepLinks routes to correct handler`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + + // Test Onramp + every { mockedUri.host } returns "onramp" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Test Sell + every { mockedUri.host } returns "redirect_sell" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Test Token Details + every { mockedUri.host } returns "token" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { + tokenDetailsDeepLinkFactory.create( + eq(testScope), + eq(mapOf("param" to "value")), + eq(isFromOnNewIntent), + ) + } + + // Test Staking + every { mockedUri.host } returns "staking" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { stakingDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Test Market Token Detail + every { mockedUri.host } returns "token_chart" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { marketsTokenDetailDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Reset params + every { mockedUri.queryParameterNames } returns emptySet() + every { mockedUri.getQueryParameter(any()) } returns "" + + // Test Buy + every { mockedUri.host } returns "redirect" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { buyDeepLinkFactory.create(eq(testScope)) } + + // Test Referral + every { mockedUri.host } returns "referral" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { referralDeepLinkFactory.create() } + + // Test Wallet + every { mockedUri.host } returns "main" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { walletDeepLinkFactory.create() } + + // Test Markets + every { mockedUri.host } returns "markets" + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + advanceUntilIdle() + verify { marketsDeepLinkFactory.create() } + } + + @Test + fun `handleTangemDeepLinks incorrect host`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "unknown" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) + + advanceUntilIdle() + + verify(inverse = true) { + onrampDeepLinkFactory.create(any(), any()) + sellDeepLinkFactory.create(any(), any()) + buyDeepLinkFactory.create(any()) + referralDeepLinkFactory.create() + walletConnectDeepLinkFactory.create(any()) + walletDeepLinkFactory.create() + tokenDetailsDeepLinkFactory.create(any(), any(), any()) + } + } + + @Test + fun `getParams filters malicious parameters`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"e=O%27Brien" + every { mockedUri.queryParameterNames } returns setOf("safe", "malicious", "quote") + every { mockedUri.getQueryParameter("safe") } returns "ok" + every { mockedUri.getQueryParameter("malicious") } returns "