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..0c9d9ac315 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)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cac33d689c..13f1801543 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -128,7 +128,7 @@
@@ -150,7 +150,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..52a6dc9646 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
@@ -142,4 +143,6 @@ interface ApplicationEntryPoint {
fun getOnlineCardVerifier(): OnlineCardVerifier
fun getUserWalletBuilderFactory(): UserWalletBuilder.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..9e44bb3dc2 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -19,16 +19,19 @@ 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.navigation.email.EmailSender
import com.tangem.core.ui.UiDependencies
@@ -48,6 +51,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,6 +71,7 @@ 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 dagger.hilt.android.AndroidEntryPoint
@@ -171,6 +176,15 @@ 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
+
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow
@@ -223,9 +237,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)
}
lifecycle.addObserver(WindowObscurationObserver)
@@ -341,7 +355,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 +391,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
if (intent != null) {
- deepLinksRegistry.launch(intent)
+ handleDeepLink(intent)
}
}
@@ -455,9 +472,24 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
}
+ if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) {
+ handleDeepLink(intent)
+ }
+
viewModel.checkForUnfinishedBackup()
}
+ private fun handleDeepLink(intent: Intent) {
+ if (routingFeatureToggle.isDeepLinkNavigationEnabled) {
+ val deepLinkExtras = intent.getStringExtra(DEEPLINK_KEY)?.toUri()
+ val receivedDeepLink = intent.data ?: deepLinkExtras ?: return
+
+ deeplinkFactory.handleDeeplink(deeplinkUri = receivedDeepLink, coroutineScope = lifecycleScope)
+ } 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..03c39c9727 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
@@ -228,6 +229,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val userWalletBuilderFactory: UserWalletBuilder.Factory
get() = entryPoint.getUserWalletBuilderFactory()
+ private val apiConfigsManager: ApiConfigsManager
+ get() = entryPoint.getApiConfigsManager()
+
// endregion
private val appScope = MainScope()
@@ -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())
}
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/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/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt
index b55ef9a8f7..e32ac8b237 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,7 +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
@@ -70,7 +69,6 @@ internal object ManageTokensDomainModule {
customTokensRepository: CustomTokensRepository,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
- networksRepository: NetworksRepository,
derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
@@ -83,7 +81,6 @@ internal object ManageTokensDomainModule {
customTokensRepository = customTokensRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
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..408e20a9b6 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,7 +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
@@ -72,7 +71,6 @@ object MarketsDomainModule {
derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository,
currenciesRepository: CurrenciesRepository,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
@@ -84,7 +82,6 @@ object MarketsDomainModule {
derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository,
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
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..14df43efb0 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,13 @@
package com.tangem.tap.di.domain
+import com.tangem.core.configtoggle.feature.FeatureTogglesManager
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.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 +31,10 @@ internal object NotificationsDomainModule {
@Singleton
fun providesSendPushTokenUseCase(
notificationsRepository: NotificationsRepository,
- getApplicationIdUseCase: GetApplicationIdUseCase,
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
): SendPushTokenUseCase {
return SendPushTokenUseCase(
notificationsRepository = notificationsRepository,
- getApplicationIdUseCase = getApplicationIdUseCase,
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
)
}
@@ -57,4 +58,10 @@ internal object NotificationsDomainModule {
notificationsRepository = notificationsRepository,
)
}
+
+ @Provides
+ @Singleton
+ fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles {
+ return DefaultNotificationsFeatureToggles(featureTogglesManager = featureTogglesManager)
+ }
}
\ 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..c678251ad9 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,10 @@ 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.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@@ -37,7 +41,6 @@ internal object TokensDomainModule {
@Singleton
fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
quotesRepository: QuotesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
@@ -47,7 +50,6 @@ internal object TokensDomainModule {
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
stakingRepository = stakingRepository,
quotesRepository = quotesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
@@ -62,7 +64,6 @@ internal object TokensDomainModule {
fun provideFetchTokenListUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
@@ -71,7 +72,6 @@ internal object TokensDomainModule {
): FetchTokenListUseCase {
return FetchTokenListUseCase(
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
quotesRepository = quotesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
@@ -121,8 +121,8 @@ internal object TokensDomainModule {
fun provideGetCurrencyUseCase(
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
- ): GetCurrencyStatusUpdatesUseCase {
- return GetCurrencyStatusUpdatesUseCase(
+ ): GetSingleCryptoCurrencyStatusUseCase {
+ return GetSingleCryptoCurrencyStatusUseCase(
currencyStatusOperations = baseCurrencyStatusOperations,
dispatchers = dispatchers,
)
@@ -147,7 +147,6 @@ internal object TokensDomainModule {
fun provideGetCurrencyWarningsUseCase(
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
- networksRepository: NetworksRepository,
currencyChecksRepository: CurrencyChecksRepository,
dispatchers: CoroutineDispatcherProvider,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
@@ -155,31 +154,17 @@ 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,7 +173,6 @@ internal object TokensDomainModule {
): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase(
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
quotesRepository = quotesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
@@ -203,7 +187,6 @@ internal object TokensDomainModule {
fun provideFetchCardTokenListUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
@@ -212,7 +195,6 @@ internal object TokensDomainModule {
): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase(
currenciesRepository = currenciesRepository,
- networksRepository = networksRepository,
quotesRepository = quotesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
@@ -222,14 +204,6 @@ internal object TokensDomainModule {
)
}
- @Provides
- @Singleton
- fun providesGetCryptoCurrencyStatusSyncUseCase(
- currencyStatusOperations: BaseCurrencyStatusOperations,
- ): GetCryptoCurrencyStatusSyncUseCase {
- return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations)
- }
-
@Provides
@Singleton
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
@@ -328,14 +302,10 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideUpdateDelayedCurrencyStatusUseCase(
- networksRepository: NetworksRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
- tokensFeatureToggles: TokensFeatureToggles,
): UpdateDelayedNetworkStatusUseCase {
return UpdateDelayedNetworkStatusUseCase(
- networksRepository = networksRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
- tokensFeatureToggles = tokensFeatureToggles,
)
}
@@ -413,7 +383,6 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
quotesRepositoryV2: QuotesRepositoryV2,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
@@ -427,7 +396,6 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
quotesRepositoryV2 = quotesRepositoryV2,
- networksRepository = networksRepository,
stakingRepository = stakingRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
@@ -447,7 +415,6 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
quotesRepositoryV2: QuotesRepositoryV2,
- networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
@@ -461,7 +428,6 @@ internal object TokensDomainModule {
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..1fbf5cb42e 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
@@ -6,6 +6,8 @@ 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 +30,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 +115,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 +213,36 @@ 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,
+ )
+ }
}
\ 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/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt
index 9b04ab7d07..c41b7b37f0 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
@@ -15,9 +15,9 @@ import com.tangem.data.common.currency.getNetwork
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.operations.derivation.ExtendedPublicKeysMap
@@ -41,7 +41,7 @@ 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(
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..d05c693542 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
@@ -8,10 +8,10 @@ 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 +49,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 = Blockchain.fromId(id = network.rawId)
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
@@ -74,7 +74,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 = Blockchain.fromId(id = rawId)
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..bfb76fbf06 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,9 +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")
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/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt
index 689db8c1c0..0ac30a5cb0 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..a9009dc947 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
@@ -3,8 +3,8 @@ package com.tangem.tap.domain.walletconnect2.domain
import arrow.core.flatten
import com.tangem.blockchain.common.Blockchain
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,7 @@ 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.usecase.GetSelectedWalletUseCase
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.filterNotNull
@@ -139,7 +140,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 +398,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)
@@ -410,7 +419,7 @@ class WalletConnectInteractor(
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List): List {
val walletManagers = networks.mapNotNull {
- val blockchain = Blockchain.fromId(it.id.value)
+ val blockchain = Blockchain.fromId(it.rawId)
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/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/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
index c00ae2518f..7dee2ef45c 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,9 @@ 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.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 +46,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 +68,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 +78,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 +96,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,16 +139,41 @@ 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()
@@ -134,18 +184,10 @@ internal class MainViewModel @Inject constructor(
.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 +385,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..a8fec8347f 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
@@ -5,10 +5,10 @@ import com.tangem.blockchain.common.Blockchain
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.tap.common.analytics.events.Token
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
@@ -79,7 +79,7 @@ object TradeCryptoMiddleware {
val status = action.cryptoCurrencyStatus
val currency = status.currency
- val blockchain = Blockchain.fromId(currency.network.id.value)
+ val blockchain = Blockchain.fromId(currency.network.rawId)
val exchangeManager = store.state.globalState.exchangeManager
val topUrl = exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
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..be40dc6f87 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
@@ -13,4 +13,10 @@ internal class DefaultAuthProvider(private val userWalletsListManager: UserWalle
override fun getCardId(): String {
return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: ""
}
+
+ override fun getCardsPublicKeys(): Map {
+ return userWalletsListManager.userWalletsSync.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..58b0438ca7 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
@@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CryptoCurrencyFactory
-import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
@@ -44,7 +44,7 @@ internal class CryptoCurrencyConverter(
}
override fun convertBack(value: CryptoCurrency): Currency {
- val blockchain = Blockchain.fromId(value.network.id.value)
+ val blockchain = Blockchain.fromId(value.network.rawId)
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..9b46887dcd 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
@@ -10,9 +10,9 @@ import com.tangem.blockchain.extensions.Result
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 = Blockchain.fromId(cryptoCurrency.network.rawId)
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..97bd71f241 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
@@ -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 = Blockchain.fromId(cryptoCurrency.network.rawId)
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..7bf0f6d0cf 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
@@ -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 = Blockchain.fromId(cryptoCurrency.network.rawId)
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
val moonpayCurrency = status?.availableForSell?.firstOrNull {
when (cryptoCurrency) {
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..f1cd71bbe3 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
@@ -291,13 +291,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,
)
}
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..45bde26900
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
@@ -0,0 +1,142 @@
+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.onramp.deeplink.BuyDeepLinkHandler
+import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
+import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
+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 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 permittedAppRoute = MutableStateFlow(false)
+
+ private var lastDeepLink: Uri? = null
+ private val deepLinkHandlerJobHolder = JobHolder()
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun handleDeeplink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
+ lastDeepLink = deeplinkUri
+
+ Timber.i(
+ """
+ Received deep link intent
+ |- Received URI: $deeplinkUri
+ """.trimIndent(),
+ )
+ permittedAppRoute
+ .transformLatest { isPermitted ->
+ if (isPermitted) {
+ lastDeepLink?.let {
+ launchDeepLink(it, coroutineScope)
+ }
+ 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) {
+ when (deeplinkUri.scheme) {
+ DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope)
+ 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) {
+ 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, 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.validate() && paramValue?.validate() == true) {
+ params[paramName] = paramValue
+ }
+ }
+
+ return params
+ }
+
+ /**
+ * Check for malicious symbol in uri part
+ */
+ private fun String.validate(): Boolean {
+ val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
+
+ return !regex.containsMatchIn(this)
+ }
+
+ private companion object {
+ const val DEEPLINK_VALIDATION_REGEX = "['\";<>()+\\\\]"
+ }
+}
\ No newline at end of file
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..32be06bd2f
--- /dev/null
+++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
@@ -0,0 +1,324 @@
+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.onramp.deeplink.BuyDeepLinkHandler
+import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
+import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
+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()) } returns mockk()
+ }
+
+ private val mockedUri = mockk(relaxed = true)
+
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+
+ private val deepLinkFactory = DeepLinkFactory(
+ onrampDeepLinkFactory,
+ sellDeepLinkFactory,
+ buyDeepLinkFactory,
+ referralDeepLinkFactory,
+ walletConnectDeepLinkFactory,
+ walletDeepLinkFactory,
+ tokenDetailsDeepLinkFactory,
+ )
+
+ @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)
+ 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)
+ 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)
+
+ 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)
+
+ 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)
+
+ 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())
+ }
+ }
+
+ @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)
+ advanceUntilIdle()
+ verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
+
+ // Test Sell
+ every { mockedUri.host } returns "redirect_sell"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope)
+ advanceUntilIdle()
+ verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
+
+ // Test Token Details
+ every { mockedUri.host } returns "token"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope)
+ advanceUntilIdle()
+ verify { tokenDetailsDeepLinkFactory.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)
+ advanceUntilIdle()
+ verify { buyDeepLinkFactory.create(eq(testScope)) }
+
+ // Test Referral
+ every { mockedUri.host } returns "referral"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope)
+ advanceUntilIdle()
+ verify { referralDeepLinkFactory.create() }
+
+ // Test Wallet
+ every { mockedUri.host } returns "main"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope)
+ advanceUntilIdle()
+ verify { walletDeepLinkFactory.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)
+
+ 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())
+ }
+ }
+
+ @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 "