Updated on 2026-08-14
This commit is contained in:
commit
1bac8466b1
990 changed files with 22183 additions and 7489 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="*"
|
||||
android:host="onramp"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
|
|
@ -150,7 +150,29 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="*"
|
||||
android:host="referral"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="main"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="token"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit a20134ee4be4d7e1e34c6f44089b3cb0713eacc3
|
||||
Subproject commit d6e348cf2f9a1f5a4e96fbf627fec71c2d17c9c1
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<AppThemeMode>
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
client.logEvent(eventId, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
client.logEvent(eventId, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
fbAnalytics.logEvent(
|
||||
eventConverter.convertEventName(event),
|
||||
|
|
|
|||
|
|
@ -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<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<EncodeHintType, Any>()
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
|
|
@ -21,9 +21,14 @@ internal object CardDataModule {
|
|||
fun providesDerivationsRepository(
|
||||
tangemSdkManager: TangemSdkManager,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
networkFactory: NetworkFactory,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DerivationsRepository {
|
||||
return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, excludedBlockchains, dispatchers)
|
||||
return DefaultDerivationsRepository(
|
||||
tangemSdkManager = tangemSdkManager,
|
||||
userWalletsStore = userWalletsStore,
|
||||
networkFactory = networkFactory,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
|
@ -28,6 +31,17 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDomainModule {
|
||||
|
||||
@Provides
|
||||
fun providesUserWalletsSyncDelegate(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsSyncDelegate {
|
||||
return DefaultUserWalletsSyncDelegate(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase {
|
||||
|
|
@ -102,10 +116,13 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesRenameWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletsRepository: WalletsRepository,
|
||||
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
): RenameWalletUseCase {
|
||||
return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers)
|
||||
return RenameWalletUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -197,4 +214,68 @@ internal object WalletsDomainModule {
|
|||
nftFeatureToggles = nftFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUpdateRemoteWalletsInfoUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
): UpdateRemoteWalletsInfoUseCase {
|
||||
return UpdateRemoteWalletsInfoUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSavedWalletChangesIdUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
): GetSavedWalletChangesUseCase {
|
||||
return GetSavedWalletChangesUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAssociateWalletsWithApplicationIdUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): AssociateWalletsWithApplicationIdUseCase {
|
||||
return AssociateWalletsWithApplicationIdUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSetNotificationsEnabledUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): SetNotificationsEnabledUseCase {
|
||||
return SetNotificationsEnabledUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletNotificationsEnabledUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): GetWalletNotificationsEnabledUseCase {
|
||||
return GetWalletNotificationsEnabledUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetIsNotificationsEnabledUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): GetIsNotificationsEnabledUseCase {
|
||||
return GetIsNotificationsEnabledUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
|
|
@ -11,13 +10,13 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.BackendId
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
|
@ -33,7 +32,7 @@ private typealias DerivedKeys = Map<ByteArrayKey, ExtendedPublicKeysMap>
|
|||
internal class DefaultDerivationsRepository(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val networkFactory: NetworkFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : DerivationsRepository {
|
||||
|
||||
|
|
@ -41,17 +40,16 @@ internal class DefaultDerivationsRepository(
|
|||
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
|
||||
}
|
||||
|
||||
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.ID>) {
|
||||
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.RawID>) {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
|
||||
derivePublicKeysByNetworks(
|
||||
userWalletId = userWalletId,
|
||||
networks = networkIds.mapNotNull {
|
||||
getNetwork(
|
||||
networkFactory.create(
|
||||
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -86,11 +84,10 @@ internal class DefaultDerivationsRepository(
|
|||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||
.findByNetworks(
|
||||
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
|
||||
getNetwork(
|
||||
networkFactory.create(
|
||||
blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@ package com.tangem.tap.domain.card
|
|||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||
|
|
@ -49,7 +50,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
return mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
||||
val blockchain = network.toBlockchain()
|
||||
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
||||
|
||||
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
|
||||
|
|
@ -74,7 +75,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
}
|
||||
|
||||
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||
val blockchain = Blockchain.fromId(id = this.id.value)
|
||||
val blockchain = this.toBlockchain()
|
||||
|
||||
return buildList {
|
||||
add(blockchain.getDerivationPath(curve = curve))
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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<TangemError, VisaDataToSignByCardWallet> = 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<Unit> {
|
||||
|
|
@ -294,7 +257,6 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.signData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
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<ExtendedPublicKey> {
|
||||
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<VisaCardActivationResponse> {
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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<VisaCardActivationStatus> {
|
||||
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<ExtendedPublicKey>,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
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<VisaCardActivationStatus> {
|
||||
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<VisaCardActivationStatus> {
|
||||
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<SignHashResponse> {
|
||||
val signHashCommand = SignHashCommand(
|
||||
hash = nonce.hexToBytes(),
|
||||
walletPublicKey = publicKey,
|
||||
derivationPath = derivationPath,
|
||||
): CompletionResult<AttestWalletKeyResponse> {
|
||||
val signHashCommand = AttestWalletKeyTask(
|
||||
publicKey = publicKey,
|
||||
challenge = nonce.hexToBytes(),
|
||||
)
|
||||
return suspendCancellableCoroutine {
|
||||
signHashCommand.run(session) { result ->
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import arrow.core.flatten
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
|
|
@ -12,6 +12,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,8 @@ class WalletConnectInteractor(
|
|||
|
||||
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
|
||||
val walletManagers = networks.mapNotNull {
|
||||
val blockchain = Blockchain.fromId(it.id.value)
|
||||
val blockchain = it.toBlockchain()
|
||||
|
||||
walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<WalletConnectState> {
|
||||
|
||||
private val model: WalletConnectModel = getOrCreateModel()
|
||||
private val model: WalletConnectModel = getOrCreateModel(params)
|
||||
|
||||
private var screenState: MutableState<WalletConnectScreenState> =
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WalletConnectComponent.Params>()
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Unit, WalletConnectComponent>
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
interface Factory : ComponentFactory<Params, WalletConnectComponent>
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.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 = currency.network.toBlockchain()
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
val topUrl = exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
|
|
|
|||
|
|
@ -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<String, String> {
|
||||
return userWalletsListManager.userWalletsSync.associate {
|
||||
it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,9 @@ package com.tangem.tap.network.exchangeServices
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -44,7 +45,7 @@ internal class CryptoCurrencyConverter(
|
|||
}
|
||||
|
||||
override fun convertBack(value: CryptoCurrency): Currency {
|
||||
val blockchain = Blockchain.fromId(value.network.id.value)
|
||||
val blockchain = value.network.toBlockchain()
|
||||
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> Currency.Blockchain(
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import com.tangem.Message
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -68,7 +68,7 @@ class CurrencyExchangeManager(
|
|||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
||||
|
||||
val urlBuilder = getExchangeUrlBuilder(action)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
|
|
@ -10,8 +10,8 @@ import com.tangem.data.onramp.legacy.mercuryoNetwork
|
|||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
|
|
@ -77,7 +77,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
|||
): String {
|
||||
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
|
||||
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
|
||||
val builder = Uri.Builder()
|
||||
.scheme(ExchangeUrlBuilder.SCHEME)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.network.exchangeServices.moonpay
|
|||
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
|
|
@ -10,8 +10,8 @@ import com.tangem.domain.common.extensions.withIOContext
|
|||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
|
|
@ -133,7 +133,7 @@ class MoonPayService(
|
|||
): String? {
|
||||
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
|
||||
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
|
||||
val moonpayCurrency = status?.availableForSell?.firstOrNull {
|
||||
when (cryptoCurrency) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Boolean, Unit> { 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<String, String> {
|
||||
val params = mutableMapOf<String, String>()
|
||||
|
||||
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 = "['\";<>()+\\\\]"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.configs.GenericCardConfig
|
||||
|
|
@ -32,7 +33,7 @@ internal class DefaultDerivationsRepositoryTest {
|
|||
tangemSdkManager = tangemSdkManager,
|
||||
userWalletsStore = userWalletsStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
excludedBlockchains = ExcludedBlockchains(),
|
||||
networkFactory = NetworkFactory(excludedBlockchains = ExcludedBlockchains()),
|
||||
)
|
||||
|
||||
private val defaultUserWalletId = UserWalletId("011")
|
||||
|
|
|
|||
|
|
@ -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<OnrampDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val sellDeepLinkFactory = mockk<SellDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val buyDeepLinkFactory = mockk<BuyDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any()) } returns mockk()
|
||||
}
|
||||
private val referralDeepLinkFactory = mockk<ReferralDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
private val walletConnectDeepLinkFactory = mockk<WalletConnectDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any()) } returns mockk()
|
||||
}
|
||||
private val walletDeepLinkFactory = mockk<WalletDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
private val tokenDetailsDeepLinkFactory = mockk<TokenDetailsDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
|
||||
private val mockedUri = mockk<Uri>(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 "<script>"
|
||||
every { mockedUri.getQueryParameter("quote") } returns "O'Brien"
|
||||
|
||||
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validate detects malicious characters`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
every { mockedUri.host } returns "onramp"
|
||||
|
||||
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||
|
||||
every { mockedUri.query } returns "safe=ok"
|
||||
every { mockedUri.queryParameterNames } returns setOf("safe")
|
||||
every { mockedUri.getQueryParameter("safe") } returns "ok"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
|
||||
|
||||
every { mockedUri.query } returns "param123=ok"
|
||||
every { mockedUri.queryParameterNames } returns setOf("param123")
|
||||
every { mockedUri.getQueryParameter("param123") } returns "ok"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param123" to "ok"))) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=<script>"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "<script>"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=O'Brien"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "O'Brien"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=test;"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "test;"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=test+attack"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "test+attack"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=test\\path"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "test\\path"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue