Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-17 12:32:52 +01:00
commit e8ccd67098
295 changed files with 4414 additions and 2971 deletions

View file

@ -42,8 +42,8 @@ configurations.all {
dependencies {
implementation(files("libs/walletconnect-1.5.6.aar"))
implementation(projects.domain.legacy)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.models)
implementation(projects.domain.core)
implementation(projects.domain.card)
@ -80,6 +80,7 @@ dependencies {
implementation(projects.core.deepLinks)
implementation(projects.libs.crypto)
implementation(projects.libs.auth)
implementation(projects.libs.blockchainSdk)
implementation(projects.data.appCurrency)
implementation(projects.data.appTheme)
@ -87,7 +88,6 @@ dependencies {
implementation(projects.data.card)
implementation(projects.data.common)
implementation(projects.data.settings)
implementation(projects.data.source.preferences)
implementation(projects.data.tokens)
implementation(projects.data.txhistory)
implementation(projects.data.wallets)
@ -216,9 +216,12 @@ dependencies {
androidTestImplementation(deps.test.kaspresso.compose)
androidTestImplementation(deps.test.compose.junit)
androidTestImplementation(deps.test.hamcrest)
androidTestImplementation(deps.test.hilt)
kaptAndroidTest(deps.test.hilt.compiler)
/** Chucker */
debugImplementation(deps.chucker)
mockedImplementation(deps.chuckerStub)
externalImplementation(deps.chuckerStub)
internalImplementation(deps.chuckerStub)
releaseImplementation(deps.chuckerStub)

View file

@ -0,0 +1,28 @@
package com.tangem.common
import androidx.test.core.app.ApplicationProvider
import com.tangem.tap.ApplicationEntryPoint
import com.tangem.tap.TangemApplication
import dagger.hilt.android.testing.OnComponentReadyRunner
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class ApplicationInjectionExecutionRule : TestRule {
private val tangemApplication: TangemApplication
get() = ApplicationProvider.getApplicationContext()
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
OnComponentReadyRunner.addListener(
tangemApplication, ApplicationEntryPoint::class.java
) { _: ApplicationEntryPoint ->
tangemApplication.init()
}
base.evaluate()
}
}
}
}

View file

@ -0,0 +1,57 @@
package com.tangem.common
import android.Manifest
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.espresso.intent.Intents
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import com.kaspersky.components.composesupport.config.withComposeSupport
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.tangem.tap.MainActivity
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.hilt.android.testing.HiltAndroidRule
import org.junit.Rule
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import javax.inject.Inject
@RunWith(AndroidJUnit4::class)
abstract class BaseTestCase : TestCase(
kaspressoBuilder = Kaspresso.Builder.withComposeSupport()
) {
@Inject
lateinit var tangemSdkManager: TangemSdkManager
@get:Rule
open val composeTestRule = createAndroidComposeRule<MainActivity>()
@get:Rule
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.POST_NOTIFICATIONS,
Manifest.permission.CAMERA
)
private val hiltRule = HiltAndroidRule(this)
@Rule
@JvmField
val ruleChain = RuleChain
.outerRule(hiltRule)
.around(ApplicationInjectionExecutionRule())
protected fun setupHooks(
additionalBeforeSection: () -> Unit = {},
additionalAfterSection: () -> Unit = {},
) = before {
hiltRule.inject()
Intents.init()
additionalBeforeSection()
}.after {
additionalAfterSection()
Intents.release()
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.common
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import com.tangem.common.di.TangemMockedApplication_Application
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, TangemMockedApplication_Application::class.java.name, context)
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.common
import com.tangem.tap.TangemApplication
open class TangemEmptyApplication : TangemApplication() {
override fun onCreate() {
// super.onCreate() is not called intentionally
}
}

View file

@ -0,0 +1,7 @@
package com.tangem.common.di
import com.tangem.common.TangemEmptyApplication
import dagger.hilt.android.testing.CustomTestApplication
@CustomTestApplication(TangemEmptyApplication::class)
internal class TangemMockedApplication

View file

@ -0,0 +1,28 @@
package com.tangem.common.di
import android.content.Context
import com.tangem.tap.di.TangemSdkManagerModule
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.Module
import dagger.Provides
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import javax.inject.Singleton
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [TangemSdkManagerModule::class]
)
object TestModule {
@Provides
@Singleton
fun provideTangemSdkManager(
@ApplicationContext context: Context
): TangemSdkManager {
return MockTangemSdkManager(resources = context.resources)
}
}

View file

@ -1,27 +0,0 @@
package com.tangem.helpers.base
import android.Manifest
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import com.kaspersky.components.composesupport.config.withComposeSupport
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.tangem.tap.MainActivity
import org.junit.Rule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
open class BaseAutoTestCase : TestCase(
kaspressoBuilder = Kaspresso.Builder.withComposeSupport()
) {
@get:Rule
open val composeTestRule = createAndroidComposeRule<MainActivity>()
@get: Rule
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.POST_NOTIFICATIONS,
Manifest.permission.CAMERA
)
}

View file

@ -0,0 +1,17 @@
package com.tangem.screens
import com.kaspersky.kaspresso.screens.KScreen
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.wallet.R
import io.github.kakaocup.kakao.text.KButton
object DisclaimerScreen : KScreen<DisclaimerScreen>(){
override val layoutId = R.layout.fragment_disclaimer
override val viewClass = DisclaimerFragment::class.java
val acceptButton: KButton = KButton {
withId(R.id.btn_accept)
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.tap.common.compose.resources.C
import com.tangem.core.ui.test.TestTags
import com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.KNode
@ -11,15 +11,15 @@ import io.github.kakaocup.kakao.text.KButton
class StoriesScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<StoriesScreen>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(C.Tag.STORIES_SCREEN) }
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) }
) {
val scanButton: KNode = child {
hasTestTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON)
hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON)
}
val orderButton: KNode = child {
hasTestTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON)
hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON)
}
val enableNFCAlert: KView = KView {

View file

@ -0,0 +1,11 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.core.ui.test.TestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
class WalletScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletScreen>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.WALLET_SCREEN) }
)

View file

@ -0,0 +1,39 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.screens.DisclaimerScreen
import com.tangem.screens.StoriesScreen
import com.tangem.screens.WalletScreen
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import org.junit.Test
@HiltAndroidTest
class MainScreenTest : BaseTestCase() {
@Test
fun goToMain() =
setupHooks().run {
ComposeScreen.onComposeScreen<StoriesScreen>(composeTestRule) {
step("Click on \"Scan\" button") {
scanButton {
assertIsDisplayed()
performClick()
}
}
}
DisclaimerScreen {
step("Click on \"Accept\" button") {
acceptButton {
isVisible()
click()
}
}
}
ComposeScreen.onComposeScreen<WalletScreen>(composeTestRule) {
step("Make sure wallet screen is visible") {
assertIsDisplayed()
}
}
}
}

View file

@ -1,22 +1,20 @@
package com.tangem.tests
import android.content.Intent.ACTION_VIEW
import androidx.test.espresso.intent.Intents
import com.tangem.helpers.base.BaseAutoTestCase
import com.tangem.common.BaseTestCase
import com.tangem.screens.StoriesScreen
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.kakao.intent.KIntent
import org.junit.Test
class StoriesTest : BaseAutoTestCase() {
@HiltAndroidTest
class StoriesTest : BaseTestCase() {
@Test
fun clickOnButtons() = before {
Intents.init()
}.after {
Intents.release()
}.run {
fun clickOnButtons() =
setupHooks().run {
ComposeScreen.onComposeScreen<StoriesScreen>(composeTestRule) {
step("Click on \"Scan\" button") {
scanButton {

View file

@ -36,7 +36,7 @@
</queries>
<application
android:name="com.tangem.tap.TapApplication"
android:name="com.tangem.tap.TangemHiltApplication"
android:allowBackup="false"
android:fullBackupContent="false"
android:hardwareAccelerated="true"

View file

@ -0,0 +1,98 @@
package com.tangem.tap
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository
@EntryPoint
@InstallIn(SingletonComponent::class)
@Suppress("TooManyFunctions")
interface ApplicationEntryPoint {
fun getConfigManager(): ConfigManager
fun getAppStateHolder(): AppStateHolder
fun getAssetReader(): AssetReader
fun getFeatureTogglesManager(): FeatureTogglesManager
fun getNetworkConnectionManager(): NetworkConnectionManager
fun getCustomTokenFeatureToggles(): CustomTokenFeatureToggles
fun getWalletConnect2Repository(): WalletConnect2Repository
fun getWalletConnectSessionsRepository(): WalletConnectSessionsRepository
fun getManageTokensFeatureToggles(): ManageTokensFeatureToggles
fun getScanCardProcessor(): ScanCardProcessor
fun getAppCurrencyRepository(): AppCurrencyRepository
fun getWalletManagersFacade(): WalletManagersFacade
fun getNetworksRepository(): NetworksRepository
fun getCurrenciesRepository(): CurrenciesRepository
fun getAppThemeModeRepository(): AppThemeModeRepository
fun getBalanceHidingRepository(): BalanceHidingRepository
fun getUserTokensStore(): UserTokensStore
fun getGetAppThemeModeUseCase(): GetAppThemeModeUseCase
fun getWalletsRepository(): WalletsRepository
fun getSendFeatureToggles(): SendFeatureToggles
fun getOneTimeEventFilter(): OneTimeEventFilter
fun getGeneralUserWalletsListManager(): UserWalletsListManager
fun getWasTwinsOnboardingShownUseCase(): WasTwinsOnboardingShownUseCase
fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase
fun getCardRepository(): CardRepository
fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles
fun getTangemSdkLogger(): TangemSdkLogger
fun getSettingsRepository(): SettingsRepository
fun getBlockchainSDKFactory(): BlockchainSDKFactory
}

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
@ -14,6 +15,7 @@ import kotlin.time.Duration
internal class LockUserWalletsTimer(
owner: LifecycleOwner,
private val settingsRepository: SettingsRepository,
private val duration: Duration = with(Duration) { 10.minutes },
) : LifecycleOwner by owner,
DefaultLifecycleObserver {
@ -29,24 +31,35 @@ internal class LockUserWalletsTimer(
}
override fun onResume(owner: LifecycleOwner) {
owner.lifecycleScope.launch {
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume()
Timber.d(
"""
Owner resumed
|- Was stopped: ${preferencesStorage.wasApplicationStopped}
|- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume}
|- Was stopped: $wasApplicationStopped
|- Need to open welcome screen: $shouldOpenWelcomeScreenOnResume
""".trimIndent(),
)
preferencesStorage.wasApplicationStopped = false
settingsRepository.setWasApplicationStopped(value = false)
start()
if (preferencesStorage.shouldOpenWelcomeScreenOnResume) {
if (shouldOpenWelcomeScreenOnResume) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
preferencesStorage.shouldOpenWelcomeScreenOnResume = false
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
}
}
}
override fun onStop(owner: LifecycleOwner) {
Timber.d("Owner stopped")
preferencesStorage.wasApplicationStopped = true
owner.lifecycleScope.launch {
settingsRepository.setWasApplicationStopped(value = true)
}
}
override fun onDestroy(owner: LifecycleOwner) {
@ -91,27 +104,30 @@ internal class LockUserWalletsTimer(
private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) {
val startTime = System.currentTimeMillis()
delay(duration)
if (isActive) {
val userWalletsListManager = userWalletsListManagerSafe?.asLockable()
?: return@launch
val userWalletsListManager = userWalletsListManagerSafe?.asLockable() ?: return@launch
if (userWalletsListManager.hasUserWallets) {
val currentTime = System.currentTimeMillis()
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
Timber.d(
"""
Finished
|- App is stopped: ${preferencesStorage.wasApplicationStopped}
|- App is stopped: $wasApplicationStopped
|- Millis passed: ${currentTime - startTime}
""".trimIndent(),
)
if (preferencesStorage.wasApplicationStopped) {
preferencesStorage.shouldOpenWelcomeScreenOnResume = true
userWalletsListManager.lock()
if (wasApplicationStopped) {
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
} else {
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Welcome))
}
userWalletsListManager.lock()
}
}
}

View file

@ -39,8 +39,8 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
@ -57,8 +57,7 @@ import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
@ -94,11 +93,13 @@ private val mainCoroutineContext: CoroutineContext
get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope")
val mainScope = CoroutineScope(mainCoroutineContext)
// TODO: Move to DI
val userWalletsListManagerSafe: UserWalletsListManager?
get() = store.state.globalState.userWalletsListManager
val userWalletsListManager: UserWalletsListManager
get() = userWalletsListManagerSafe!!
// TODO: will be remove in this task [REDACTED_JIRA]
@Deprecated(message = "Provide UserWalletsListManager using DI")
val userWalletsListManagerSafe: UserWalletsListManager? get() = store.state.globalState.userWalletsListManager
// TODO: will be remove in this task [REDACTED_JIRA]
@Deprecated(message = "Provide UserWalletsListManager using DI")
val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!!
@Suppress("LargeClass")
@AndroidEntryPoint
@ -145,7 +146,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lateinit var deepLinksRegistry: DeepLinksRegistry
@Inject
lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase
@ -170,11 +171,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
val splashScreen = installSplashScreen()
installAppTheme() // We need to call it before onCreate to prevent unnecessary activity recreation
super.onCreate(savedInstanceState)
splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown }
installActivityDependencies()
observeAppThemeModeUpdates()
@ -217,7 +221,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
lockUserWalletsTimer = LockUserWalletsTimer(owner = this, settingsRepository = settingsRepository)
initIntentHandlers()
@ -264,9 +268,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode?> {
val tapApplication = application as TapApplication
val tangemApplication = application as TangemApplication
return tapApplication.getAppThemeModeUseCase()
return tangemApplication.getAppThemeModeUseCase()
.map { maybeMode ->
maybeMode.getOrElse { AppThemeMode.DEFAULT }
}
@ -437,15 +441,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
val canSaveWallets = if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
runCatching { userWalletsListManager.asLockable()?.isLockedSync }
val canSaveWallets = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
} else {
userWalletsListManager is BiometricUserWalletsListManager
}
val hasSavedWallets = userWalletsListManager.hasUserWallets
if (canSaveWallets && hasSavedWallets) {
if (canSaveWallets && userWalletsListManager.hasUserWallets) {
store.dispatch(
NavigationAction.NavigateTo(
screen = AppScreen.Welcome,

View file

@ -11,17 +11,14 @@ import com.orhanobut.logger.Logger
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.AccountCreator
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.FeaturesLocalLoader
import com.tangem.datasource.config.models.Config
@ -37,11 +34,11 @@ import com.tangem.domain.common.LogConfig
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -61,134 +58,123 @@ import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.product.DerivationsFinder
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import dagger.hilt.EntryPoints
import kotlinx.coroutines.runBlocking
import org.rekotlin.Store
import timber.log.Timber
import javax.inject.Inject
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository
lateinit var store: Store<AppState>
lateinit var foregroundActivityObserver: ForegroundActivityObserver
lateinit var activityResultCaller: ActivityResultCaller
lateinit var preferencesStorage: PreferencesDataSource
lateinit var walletConnectRepository: WalletConnectRepository
internal lateinit var derivationsFinder: DerivationsFinder
@HiltAndroidApp
internal class TapApplication : Application(), ImageLoaderFactory {
abstract class TangemApplication : Application(), ImageLoaderFactory {
// region Injected
@Inject
lateinit var appStateHolder: AppStateHolder
private val entryPoint: ApplicationEntryPoint
get() = EntryPoints.get(this, ApplicationEntryPoint::class.java)
@Inject
lateinit var configManager: ConfigManager
private val appStateHolder: AppStateHolder
get() = entryPoint.getAppStateHolder()
@Inject
lateinit var assetReader: AssetReader
private val configManager: ConfigManager
get() = entryPoint.getConfigManager()
@Inject
lateinit var featureTogglesManager: FeatureTogglesManager
private val assetReader: AssetReader
get() = entryPoint.getAssetReader()
@Inject
lateinit var networkConnectionManager: NetworkConnectionManager
private val featureTogglesManager: FeatureTogglesManager
get() = entryPoint.getFeatureTogglesManager()
@Inject
lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles
private val networkConnectionManager: NetworkConnectionManager
get() = entryPoint.getNetworkConnectionManager()
@Inject
lateinit var preferencesDataSource: PreferencesDataSource
private val customTokenFeatureToggles: CustomTokenFeatureToggles
get() = entryPoint.getCustomTokenFeatureToggles()
@Inject
lateinit var walletConnect2Repository: WalletConnect2Repository
private val walletConnect2Repository: WalletConnect2Repository
get() = entryPoint.getWalletConnect2Repository()
@Inject
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
private val walletConnectSessionsRepository: WalletConnectSessionsRepository
get() = entryPoint.getWalletConnectSessionsRepository()
@Inject
lateinit var manageTokensFeatureToggles: ManageTokensFeatureToggles
private val manageTokensFeatureToggles: ManageTokensFeatureToggles
get() = entryPoint.getManageTokensFeatureToggles()
@Inject
lateinit var scanCardProcessor: ScanCardProcessor
private val scanCardProcessor: ScanCardProcessor
get() = entryPoint.getScanCardProcessor()
@Inject
lateinit var appCurrencyRepository: AppCurrencyRepository
private val appCurrencyRepository: AppCurrencyRepository
get() = entryPoint.getAppCurrencyRepository()
@Inject
lateinit var walletManagersFacade: WalletManagersFacade
private val walletManagersFacade: WalletManagersFacade
get() = entryPoint.getWalletManagersFacade()
@Inject
lateinit var networksRepository: NetworksRepository
private val networksRepository: NetworksRepository
get() = entryPoint.getNetworksRepository()
@Inject
lateinit var currenciesRepository: CurrenciesRepository
private val currenciesRepository: CurrenciesRepository
get() = entryPoint.getCurrenciesRepository()
@Inject
lateinit var appThemeModeRepository: AppThemeModeRepository
private val appThemeModeRepository: AppThemeModeRepository
get() = entryPoint.getAppThemeModeRepository()
@Inject
lateinit var balanceHidingRepository: BalanceHidingRepository
private val balanceHidingRepository: BalanceHidingRepository
get() = entryPoint.getBalanceHidingRepository()
@Inject
lateinit var userTokensStore: UserTokensStore
private val userTokensStore: UserTokensStore
get() = entryPoint.getUserTokensStore()
@Inject
lateinit var getAppThemeModeUseCase: GetAppThemeModeUseCase
val getAppThemeModeUseCase: GetAppThemeModeUseCase
get() = entryPoint.getGetAppThemeModeUseCase()
@Inject
lateinit var walletsRepository: WalletsRepository
private val walletsRepository: WalletsRepository
get() = entryPoint.getWalletsRepository()
@Inject
lateinit var sendFeatureToggles: SendFeatureToggles
private val sendFeatureToggles: SendFeatureToggles
get() = entryPoint.getSendFeatureToggles()
@Inject
lateinit var oneTimeEventFilter: OneTimeEventFilter
private val oneTimeEventFilter: OneTimeEventFilter
get() = entryPoint.getOneTimeEventFilter()
@Inject
lateinit var blockchainDataStorage: BlockchainDataStorage
private val generalUserWalletsListManager: UserWalletsListManager
get() = entryPoint.getGeneralUserWalletsListManager()
@Inject
lateinit var accountCreator: AccountCreator
private val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase
get() = entryPoint.getWasTwinsOnboardingShownUseCase()
@Inject
lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles
private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
get() = entryPoint.getSaveTwinsOnboardingShownUseCase()
@Inject
lateinit var generalUserWalletsListManager: UserWalletsListManager
private val cardRepository: CardRepository
get() = entryPoint.getCardRepository()
@Inject
lateinit var wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
get() = entryPoint.getFeedbackManagerFeatureToggles()
@Inject
lateinit var saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
private val tangemSdkLogger: TangemSdkLogger
get() = entryPoint.getTangemSdkLogger()
@Inject
lateinit var cardRepository: CardRepository
private val settingsRepository: SettingsRepository
get() = entryPoint.getSettingsRepository()
@Inject
lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
@Inject
lateinit var blockchainSDKLogger: BlockchainSDKLogger
@Inject
lateinit var tangemSdkLogger: TangemSdkLogger
// endregion Injected
private val blockchainSDKFactory: BlockchainSDKFactory
get() = entryPoint.getBlockchainSDKFactory()
override fun onCreate() {
super.onCreate()
init()
}
fun init() {
store = createReduxStore()
if (BuildConfig.LOG_ENABLED) {
@ -206,19 +192,12 @@ internal class TapApplication : Application(), ImageLoaderFactory {
activityResultCaller = foregroundActivityObserver
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
preferencesStorage = preferencesDataSource
walletConnectRepository = WalletConnectRepository(this)
// TODO: Try to performance and user experience.
// [REDACTED_JIRA]
runBlocking {
featureTogglesManager.init()
if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager))
} else {
initUserWalletsListManager()
}
}
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
@ -264,16 +243,14 @@ internal class TapApplication : Application(), ImageLoaderFactory {
balanceHidingRepository = balanceHidingRepository,
walletsRepository = walletsRepository,
sendFeatureToggles = sendFeatureToggles,
blockchainDataStorage = blockchainDataStorage,
accountCreator = accountCreator,
userWalletsListManagerFeatureToggles = userWalletsListManagerFeatureToggles,
generalUserWalletsListManager = generalUserWalletsListManager,
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
cardRepository = cardRepository,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
tangemSdkLogger = tangemSdkLogger,
blockchainSDKLogger = blockchainSDKLogger,
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
),
),
)
@ -373,14 +350,4 @@ internal class TapApplication : Application(), ImageLoaderFactory {
private fun initWarningMessagesManager() {
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager()))
}
private suspend fun initUserWalletsListManager() {
val manager = if (walletsRepository.shouldSaveUserWalletsSync()) {
UserWalletsListManager.provideBiometricImplementation(applicationContext)
} else {
UserWalletsListManager.provideRuntimeImplementation()
}
store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager))
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.tap
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class TangemHiltApplication : TangemApplication()

View file

@ -86,10 +86,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
context = context,
)
}
is WalletConnectDialog.ApproveWcSession ->
ApproveWcSessionDialog.create(state.dialog.session, state.dialog.networks, context)
is WalletConnectDialog.ChooseNetwork ->
ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context)
is WalletConnectDialog.ClipboardOrScanQr ->
ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.data, context)

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.analytics.events
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.extensions.filterNotNull

View file

@ -1,9 +0,0 @@
package com.tangem.tap.common.compose.resources
object C {
object Tag {
const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER"
const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON"
const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON"
}
}

View file

@ -4,8 +4,8 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.amountToCreateAccount
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder

View file

@ -3,9 +3,10 @@ package com.tangem.tap.common.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
class AccessCodeRequestPolicyMiddleware {
@ -21,8 +22,12 @@ class AccessCodeRequestPolicyMiddleware {
}
private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) {
mainScope.launch {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet,
isBiometricsRequestPolicy = shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet,
)
}
}
}

View file

@ -95,17 +95,10 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
)
}
is GlobalAction.UpdateUserWalletsListManager -> {
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (featureToggles.isGeneralManagerEnabled) {
val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
appStateHolder.userWalletsListManager = generalUserWalletsListManager
globalState.copy(userWalletsListManager = generalUserWalletsListManager)
} else {
appStateHolder.userWalletsListManager = action.manager
globalState.copy(userWalletsListManager = action.manager)
}
}
is GlobalAction.ChangeAppThemeMode -> globalState.copy(
appThemeMode = action.appThemeMode,

View file

@ -1,20 +1,18 @@
package com.tangem.tap.di
import android.content.Context
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.network.exchangeServices.DefaultRampManager
import com.tangem.tap.proxy.AppStateHolder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -25,15 +23,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal object ActivityModule {
@Provides
@Singleton
fun provideTangemSdkManager(
@ApplicationContext context: Context,
cardSdkConfigRepository: CardSdkConfigRepository,
): TangemSdkManager {
return TangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources)
}
@Provides
@Singleton
fun provideScanCardUseCase(

View file

@ -0,0 +1,32 @@
package com.tangem.tap.di
import android.content.Context
import com.tangem.domain.card.BuildConfig
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class TangemSdkManagerModule {
@Provides
@Singleton
fun provideTangemSdkManager(
@ApplicationContext context: Context,
cardSdkConfigRepository: CardSdkConfigRepository,
): TangemSdkManager {
return if (BuildConfig.MOCK_DATA_SOURCE) {
MockTangemSdkManager(resources = context.resources)
} else {
DefaultTangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources)
}
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.di.data
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.card.DefaultDerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module

View file

@ -34,14 +34,6 @@ internal object CardDomainModule {
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
}
@Provides
@ViewModelScoped
fun provideGetAccessCodeSavingStatusUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
): GetAccessCodeSavingStatusUseCase {
return GetAccessCodeSavingStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
}
@Provides
@ViewModelScoped
fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase {

View file

@ -9,7 +9,7 @@ import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
import dagger.Module
import dagger.Provides
@ -140,4 +140,20 @@ internal object SettingsDomainModule {
fun provideNeverShowTapHelpUseCase(settingsRepository: SettingsRepository): NeverShowTapHelpUseCase {
return NeverShowTapHelpUseCase(settingsRepository = settingsRepository)
}
@Provides
@ViewModelScoped
fun provideSetSaveWalletScreenShownUseCase(
settingsRepository: SettingsRepository,
): SetSaveWalletScreenShownUseCase {
return SetSaveWalletScreenShownUseCase(settingsRepository = settingsRepository)
}
@Provides
@ViewModelScoped
fun provideIncrementAppLaunchCounterUseCase(
settingsRepository: SettingsRepository,
): IncrementAppLaunchCounterUseCase {
return IncrementAppLaunchCounterUseCase(settingsRepository = settingsRepository)
}
}

View file

@ -55,9 +55,8 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetTokenListUseCase {
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository)
}
@Provides
@ -66,9 +65,8 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCardTokensListUseCase {
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository)
}
@Provides

View file

@ -1,15 +1,11 @@
package com.tangem.tap.di.domain
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.AccountCreator
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.config.ConfigManager
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.datasource.di.SdkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.walletmanager.DefaultWalletManagersFacade
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.onboarding.data.MnemonicRepository
@ -28,26 +24,18 @@ internal object WalletManagersFacadeModule {
fun provideWalletManagersFacade(
walletManagersStore: WalletManagersStore,
userWalletsStore: UserWalletsStore,
configManager: ConfigManager,
blockchainDataStorage: BlockchainDataStorage,
accountCreator: AccountCreator,
mnemonicRepository: MnemonicRepository,
assetReader: AssetReader,
@SdkMoshi moshi: Moshi,
blockchainSDKLogger: BlockchainSDKLogger,
feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
mnemonicRepository: MnemonicRepository,
blockchainSDKFactory: BlockchainSDKFactory,
): WalletManagersFacade {
return DefaultWalletManagersFacade(
walletManagersStore = walletManagersStore,
userWalletsStore = userWalletsStore,
configManager = configManager,
blockchainDataStorage = blockchainDataStorage,
assetReader = assetReader,
moshi = moshi,
mnemonic = mnemonicRepository.generateDefaultMnemonic(),
accountCreator = accountCreator,
blockchainSDKLogger = blockchainSDKLogger,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
blockchainSDKFactory = blockchainSDKFactory,
)
}
}

View file

@ -1,9 +1,7 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.datasource.config.ConfigManager
@ -12,14 +10,11 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
@ -32,29 +27,12 @@ class TapWalletManager(
private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(),
) {
private val blockchainSdkConfig by lazy {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
private var loadUserWalletDataJob: Job? = null
set(value) {
field?.cancel()
field = value
}
val walletManagerFactory: WalletManagerFactory by lazy {
WalletManagerFactory(
config = blockchainSdkConfig,
accountCreator = store.inject(DaggerGraphState::accountCreator),
blockchainDataStorage = store.inject(DaggerGraphState::blockchainDataStorage),
loggers = if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
listOf(store.inject(DaggerGraphState::blockchainSDKLogger))
} else {
emptyList()
},
)
}
suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
// If a previous job was running, it gets cancelled before the new one starts,
// ensuring that only one job is active at any given time.
@ -79,9 +57,7 @@ class TapWalletManager(
// Order is important
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
store.dispatch(WalletConnectAction.ResetState)
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
}
}

View file

@ -13,7 +13,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.card.repository.ScanCardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
// TODO: Move to the :data:card module

View file

@ -0,0 +1,98 @@
package com.tangem.tap.domain.sdk
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.common.*
import com.tangem.common.authentication.keystore.KeystoreManager
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
interface TangemSdkManager {
val canUseBiometry: Boolean
val needEnrollBiometrics: Boolean
val keystoreManager: KeystoreManager
val secureStorage: SecureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
suspend fun scanProduct(
cardId: String? = null,
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse>
suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean = false,
): CompletionResult<CreateProductWalletTaskResponse>
suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse>
suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse>
suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
): CompletionResult<ExtendedPublicKey>
suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO>
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit>
suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit>
suspend fun clearSavedUserCodes(): CompletionResult<Unit>
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult<SuccessResponse>
suspend fun scanCard(
cardId: String? = null,
allowRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<CardDTO>
suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
@DrawableRes iconScanRes: Int? = null,
): CompletionResult<T>
@Suppress("MagicNumber")
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?)
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy)
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.domain
package com.tangem.tap.domain.sdk.impl
import android.content.res.Resources
import androidx.annotation.DrawableRes
@ -29,6 +29,7 @@ import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
@ -40,10 +41,10 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
@Suppress("TooManyFunctions")
class TangemSdkManager(
class DefaultTangemSdkManager(
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val resources: Resources,
) {
) : TangemSdkManager {
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
@ -55,25 +56,25 @@ class TangemSdkManager(
)
}
val canUseBiometry: Boolean
override val canUseBiometry: Boolean
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
val needEnrollBiometrics: Boolean
override val needEnrollBiometrics: Boolean
get() = tangemSdk.authenticationManager.needEnrollBiometrics
val keystoreManager: KeystoreManager
override val keystoreManager: KeystoreManager
get() = tangemSdk.keystoreManager
val secureStorage: SecureStorage
override val secureStorage: SecureStorage
get() = tangemSdk.secureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = tangemSdk.config.userCodeRequestPolicy
suspend fun scanProduct(
cardId: String? = null,
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
override suspend fun scanProduct(
cardId: String?,
messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
@ -87,9 +88,9 @@ class TangemSdkManager(
).also { sendScanResultsToAnalytics(it) }
}
suspend fun createProductWallet(
override suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean = false,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
return runTaskAsync(
runnable = CreateProductWalletTask(
@ -103,7 +104,7 @@ class TangemSdkManager(
)
}
suspend fun importWallet(
override suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
@ -135,14 +136,14 @@ class TangemSdkManager(
}
}
suspend fun derivePublicKeys(
override suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse> {
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
}
suspend fun deriveExtendedPublicKey(
override suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
@ -153,7 +154,7 @@ class TangemSdkManager(
)
}
suspend fun resetToFactorySettings(
override suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
@ -167,7 +168,7 @@ class TangemSdkManager(
.map { CardDTO(it) }
}
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
override suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
return userCodeRepository.save(
cardsIds = cardsIds,
userCode = UserCode(
@ -177,15 +178,15 @@ class TangemSdkManager(
)
}
suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
override suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
return userCodeRepository.delete(cardsIds.toSet())
}
suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
override suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
return userCodeRepository.clear()
}
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
@ -193,7 +194,7 @@ class TangemSdkManager(
)
}
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
@ -201,7 +202,7 @@ class TangemSdkManager(
)
}
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(),
cardId,
@ -209,7 +210,10 @@ class TangemSdkManager(
)
}
suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult<SuccessResponse> {
override suspend fun setAccessCodeRecoveryEnabled(
cardId: String?,
enabled: Boolean,
): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled),
cardId,
@ -217,9 +221,9 @@ class TangemSdkManager(
)
}
suspend fun scanCard(
cardId: String? = null,
allowRequestAccessCodeFromRepository: Boolean = false,
override suspend fun scanCard(
cardId: String?,
allowRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository),
@ -229,12 +233,12 @@ class TangemSdkManager(
.map { CardDTO(it) }
}
suspend fun <T> runTaskAsync(
override suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
@DrawableRes iconScanRes: Int? = null,
cardId: String?,
initialMessage: Message?,
accessCode: String?,
@DrawableRes iconScanRes: Int?,
): CompletionResult<T> = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result ->
@ -253,7 +257,7 @@ class TangemSdkManager(
}
@Suppress("MagicNumber")
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
tangemSdk.config.cardIdDisplayFormat = when {
scanResponse == null -> CardIdDisplayFormat.Full
scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
@ -262,11 +266,11 @@ class TangemSdkManager(
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
}
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
tangemSdk.config.userCodeRequestPolicy = policy
}

View file

@ -0,0 +1,149 @@
package com.tangem.tap.domain.sdk.impl
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.common.*
import com.tangem.common.authentication.keystore.KeystoreManager
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Suppress("TooManyFunctions")
class MockTangemSdkManager(
private val resources: Resources,
) : TangemSdkManager {
override val canUseBiometry: Boolean
get() = false
override val needEnrollBiometrics: Boolean
get() = TODO()
override val keystoreManager: KeystoreManager
get() = TODO()
override val secureStorage: SecureStorage
get() = TODO()
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = TODO()
override suspend fun scanProduct(
cardId: String?,
messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
return CompletionResult.Success(MockProvider.getScanResponse())
}
override suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
TODO()
}
override suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
TODO()
}
override suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse> {
TODO()
}
override suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
): CompletionResult<ExtendedPublicKey> {
TODO()
}
override suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
TODO()
}
override suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
TODO()
}
override suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
TODO()
}
override suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
TODO()
}
override suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setAccessCodeRecoveryEnabled(
cardId: String?,
enabled: Boolean,
): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun scanCard(
cardId: String?,
allowRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
TODO()
}
override suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String?,
initialMessage: Message?,
accessCode: String?,
@DrawableRes iconScanRes: Int?,
): CompletionResult<T> = withContext(Dispatchers.Main) {
TODO()
}
@Suppress("MagicNumber")
override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
}
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
TODO()
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.domain.sdk.mocks
import com.tangem.domain.models.scan.ProductType
import com.tangem.tap.domain.sdk.mocks.wallet.WalletMocks
import com.tangem.tap.domain.sdk.mocks.wallet2.Wallet2Mocks
object MockProvider {
var productType: ProductType = ProductType.Wallet
fun getScanResponse() = getMocks(productType).scanResponse
private fun getMocks(productType: ProductType): Mocks {
return when (productType) {
ProductType.Wallet -> WalletMocks
ProductType.Wallet2 -> Wallet2Mocks
else -> TODO()
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.domain.sdk.mocks
import com.tangem.domain.models.scan.ScanResponse
interface Mocks {
val scanResponse: ScanResponse
}

View file

@ -0,0 +1,127 @@
package com.tangem.tap.domain.sdk.mocks.wallet
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.EncryptionMode
import com.tangem.common.card.FirmwareVersion
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.domain.sdk.mocks.Mocks
import java.util.Date
object WalletMocks : Mocks {
private val cardDto = CardDTO(
cardId = "AC05000000086747",
batchId = "AC05",
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
firmwareVersion = CardDTO.FirmwareVersion(
major = 4,
minor = 52,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1649635200000),
signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66),
),
issuer = CardDTO.Issuer(
name = "TANGEM AG",
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = false,
isSettingPasscodeAllowed = false,
isResettingUserCodesAllowed = true,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = false,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = false,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Secp256r1,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = false,
derivedKeys = mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = false,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.NoBackup,
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
}

View file

@ -0,0 +1,10 @@
package com.tangem.tap.domain.sdk.mocks.wallet2
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.sdk.mocks.Mocks
object Wallet2Mocks : Mocks {
override val scanResponse: ScanResponse
get() = TODO("Not yet implemented")
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.settings
import com.tangem.domain.settings.repositories.LegacySettingsRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
internal class DefaultLegacySettingsRepository(
private val tangemSdkManager: TangemSdkManager,

View file

@ -3,11 +3,11 @@ package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId

View file

@ -9,7 +9,7 @@ import com.tangem.common.KeyPair
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.wallet.CreateWalletResponse
@ -88,6 +88,7 @@ class TwinCardsManager(
)
}
@Deprecated(message = "Use AssetReader instead")
private fun getIssuers(reader: AssetReader): List<Issuer> {
val file = reader.readJson(fileName = "tangem-app-config/issuers")
return getAdapter().fromJson(file)!!

View file

@ -1,12 +0,0 @@
package com.tangem.tap.domain.userWalletList
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
internal class DefaultUserWalletsListManagerFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : UserWalletsListManagerFeatureToggles {
override val isGeneralManagerEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED")
}

View file

@ -1,23 +0,0 @@
package com.tangem.tap.domain.userWalletList.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.tap.domain.userWalletList.DefaultUserWalletsListManagerFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object UserWalletsListManagerFeatureTogglesModule {
@Provides
@Singleton
fun provideUserWalletsListManagerFeatureToggles(
featureTogglesManager: FeatureTogglesManager,
): UserWalletsListManagerFeatureToggles {
return DefaultUserWalletsListManagerFeatureToggles(featureTogglesManager)
}
}

View file

@ -66,7 +66,7 @@ internal object UserWalletsListManagerModule {
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
storageName = "user_wallets_storage",
),
)

View file

@ -1,86 +0,0 @@
package com.tangem.tap.domain.userWalletList.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.authentication.storage.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
import com.tangem.tap.domain.userWalletList.utils.json.*
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.Provider
internal const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
fun UserWalletsListManager.Companion.provideBiometricImplementation(
applicationContext: Context,
): UserWalletsListManager {
val moshi = Moshi.Builder()
.add(WalletDerivedKeysMapAdapter())
.add(ScanResponseDerivedKeysMapAdapter())
.add(ByteArrayKeyAdapter())
.add(ExtendedPublicKeysMapAdapter())
.add(CardBackupStatusAdapter())
.add(DerivationPathAdapterWithMigration())
.add(TangemSdkAdapter.DateAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
.add(KotlinJsonAdapterFactory())
.build()
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
),
)
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
),
keystoreManager = DelegatedKeystoreManager(
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
),
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(
secureStorage = secureStorage,
)
return BiometricUserWalletsListManager(
keysRepository = keysRepository,
publicInformationRepository = publicInformationRepository,
sensitiveInformationRepository = sensitiveInformationRepository,
selectedUserWalletRepository = selectedUserWalletRepository,
)
}
fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager {
return RuntimeUserWalletsListManager()
}

View file

@ -10,13 +10,11 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
import com.tangem.tap.features.details.redux.walletconnect.TradeData
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
import timber.log.Timber
internal object BnbHelper {
fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer {
fun createMessageData(order: WcBinanceTransferOrder): BinanceMessageData.Transfer {
val input = order.msgs.first().inputs.first()
val output = order.msgs.first().inputs.first()
@ -42,51 +40,7 @@ internal object BnbHelper {
)
}
fun WcBinanceTradeOrder.toWCBinanceTradeOrder(): WCBinanceTradeOrder {
return WCBinanceTradeOrder(
account_number = accountNumber,
chain_id = chainId,
data = data,
memo = memo,
sequence = sequence,
source = source,
msgs = msgs.map { it.toWCBinanceTradeOrderMessage() },
)
}
private fun WcBinanceTradeOrder.Message.toWCBinanceTradeOrderMessage(): WCBinanceTradeOrder.Message {
return WCBinanceTradeOrder.Message(id, orderType, price, quantity, sender, side, symbol, timeInforce)
}
fun WcBinanceTransferOrder.toWCBinanceTransferOrder(): WCBinanceTransferOrder {
return WCBinanceTransferOrder(
account_number = accountNumber,
chain_id = chainId,
data = data,
memo = memo,
sequence = sequence,
source = source,
msgs = msgs.map { it.toWCBinanceTransferOrderMessage() },
)
}
private fun WcBinanceTransferOrder.Message.toWCBinanceTransferOrderMessage(): WCBinanceTransferOrder.Message {
return WCBinanceTransferOrder.Message(
inputs.map { it.toWCBinanceItem() },
outputs.map { it.toWCBinanceItem() },
)
}
private fun WcBinanceTransferOrder.Message.Item.toWCBinanceItem(): WCBinanceTransferOrder.Message.Item {
return WCBinanceTransferOrder.Message.Item(
address,
coins.map {
WCBinanceTransferOrder.Message.Item.Coin(it.amount, it.denom)
},
)
}
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
fun createMessageData(order: WcBinanceTradeOrder): BinanceMessageData.Trade {
val address = order.msgs.first().sender
val tradeData = order.msgs.map {

View file

@ -1,562 +0,0 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.walletconnect.extensions.isDappSupported
import com.tangem.tap.domain.walletconnect.extensions.toWcEthereumSignMessage
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.walletConnectRepository
import com.trustwallet.walletconnect.WCClient
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCAddNetwork
import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.collections.set
@Suppress("LargeClass")
internal class WalletConnectManager {
private var cardId: String? = null
private val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(RetryInterceptor())
.build()
}
private val interceptor by lazy {
createNetworkLoggingInterceptor()
}
private var sessions: MutableMap<Topic, WalletConnectActiveData> = mutableMapOf()
fun connect(wcUri: String) {
val session = WCSession.from(wcUri).guard {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = null,
error = TapError.WalletConnect.UnsupportedLink,
),
)
return
}
if (sessions[session.topic] != null) {
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
return
}
val client = WCClient(httpClient = okHttpClient)
setListeners(client)
val peerId = UUID.randomUUID().toString()
try {
client.connect(session, tangemPeerMeta, peerId)
} catch (exception: IllegalArgumentException) {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = null,
error = TapError.WalletConnect.UnsupportedLink,
),
)
return
}
sessions[session.topic] = WalletConnectActiveData(
peerId = peerId,
remotePeerId = null,
session = session,
client = client,
wallet = WalletForSession(),
)
setupConnectionTimeoutCheck(session)
}
fun updateSession(session: WalletConnectSession) {
val updatedSession = sessions[session.session.topic]?.copy(
wallet = session.wallet,
)
if (updatedSession != null) {
sessions[session.session.topic] = updatedSession
}
}
fun updateBlockchain(session: WalletConnectSession) {
sessions[session.session.topic]?.client?.updateSession(
accounts = listOfNotNull(session.getAddress()),
chainId = session.wallet.blockchain?.getChainId(),
approved = true,
)
val updatedSession = sessions[session.session.topic]?.copy(
wallet = session.wallet,
)
if (updatedSession != null) {
sessions[session.session.topic] = updatedSession
}
}
@Suppress("MagicNumber")
private fun setupConnectionTimeoutCheck(session: WCSession) {
scope.launch {
delay(20_000)
val data = sessions[session.topic]
if (data != null && data.peerMeta == null) {
disconnect(session)
store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session))
}
}
}
fun restoreSessions(scanResponse: ScanResponse) {
val walletPublicKey = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }?.publicKey
?: return
if (scanResponse.card.backupStatus?.isActive != true) cardId = scanResponse.card.cardId
val sessions = walletConnectRepository.loadSavedSessions()
// filter sessions for this particular card
.filter { it.wallet.walletPublicKey.contentEquals(walletPublicKey) }
this.sessions = sessions
.map { session ->
WalletConnectActiveData(
peerId = session.peerId,
remotePeerId = session.remotePeerId,
client = WCClient(httpClient = okHttpClient),
session = session.session,
peerMeta = session.peerMeta,
wallet = session.wallet,
)
.also {
setListeners(it.client)
it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId)
}
}.associateBy { it.session.topic }.toMutableMap()
store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions))
}
fun approve(session: WCSession) {
val activeData = sessions[session.topic] ?: return
removeSimilarSessions(activeData)
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return
val blockchain = activeData.wallet.getBlockchainForSession()
val accounts = listOf(blockchain.makeAddresses(key).first().value)
val approved = activeData.client.approveSession(
accounts = accounts,
chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!,
)
if (approved) {
val walletConnectSession = WalletConnectSession(
peerId = activeData.peerId,
remotePeerId = activeData.remotePeerId,
wallet = activeData.wallet,
session = session,
peerMeta = activeData.peerMeta!!,
)
walletConnectRepository.saveSession(walletConnectSession)
store.dispatchOnMain(
WalletConnectAction.ApproveSession.Success(
walletConnectSession,
),
)
}
}
private fun removeSimilarSessions(activeData: WalletConnectActiveData) {
val sessionsToRemove = sessions.filter {
it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true &&
it.value.peerMeta?.url == activeData.peerMeta?.url &&
it.value.session != activeData.session
}
Timber.d("RemoveSimilarSessions: ${sessionsToRemove.values.map { it.client.session }}")
sessionsToRemove.forEach { disconnect(it.value.session) }
}
fun rejectRequest(topic: String, id: Long) {
val activeData = sessions[topic] ?: return
activeData.client.rejectRequest(id)
}
private fun acceptRequest(topic: String, id: Long, data: String) {
val activeData = sessions[topic] ?: return
activeData.client.approveRequest(id, data)
}
fun disconnect(session: WCSession) {
val activeData = sessions[session.topic] ?: return
val disconnected = if (activeData.client.isConnected) {
activeData.client.killSession()
} else {
true
}
if (disconnected) {
onSessionClosed(session)
}
}
private fun onSessionClosed(session: WCSession) {
sessions.remove(session.topic)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
}
fun handleTransactionRequest(
transaction: WcEthereumTransaction,
session: WalletConnectSession,
id: Long,
type: WcEthTransactionType,
) {
val activeData = sessions[session.session.topic] ?: return
scope.launch {
val data = WalletConnectSdkHelper().prepareTransactionData(
EthTransactionData(
transaction = transaction,
networkId = session.wallet.blockchain?.toNetworkId() ?: "",
rawDerivationPath = session.wallet.derivationPath?.rawPath,
id = id,
topic = session.session.topic,
type = type,
metaName = session.peerMeta.name,
metaUrl = session.peerMeta.url,
),
).guard {
sessions[session.session.topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
session.session.topic,
id,
),
)
return@launch
}
sessions[session.session.topic] = activeData.copy(transactionData = data)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.RequestTransaction(
WcPreparedRequest.EthTransaction(
preparedRequestData = data,
topic = session.session.topic,
requestId = id,
derivationPath = data.walletManager.wallet.publicKey.derivationPath?.rawPath,
),
),
),
)
}
}
fun completeTransaction(topic: Topic) {
val activeData = sessions[topic]
val data = activeData?.transactionData ?: return
scope.launch {
val hash = WalletConnectSdkHelper().completeTransaction(data, cardId).guard {
sessions[topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
data.id,
),
)
return@launch
}
acceptRequest(topic, data.id, hash)
sessions[topic] = activeData.copy(transactionData = null)
}
}
fun signBnb(id: Long, data: ByteArray, topic: Topic) {
val activeData = sessions[topic] ?: return
scope.launch {
val hash = WalletConnectSdkHelper().signBnbTransaction(
data = data,
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
derivationPath = activeData.wallet.derivationPath?.rawPath,
cardId = cardId,
).guard {
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
id,
),
)
return@launch
}
acceptRequest(topic, id, hash)
}
}
fun handlePersonalSignRequest(message: WCEthereumSignMessage, session: WalletConnectSession, id: Long) {
val activeData = sessions[session.session.topic] ?: return
scope.launch {
val data = WalletConnectSdkHelper().prepareDataForPersonalSign(
message = message.toWcEthereumSignMessage(),
topic = session.session.topic,
id = id,
metaName = session.peerMeta.name,
)
sessions[session.session.topic] = activeData.copy(personalSignData = data)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.PersonalSign(
WcPreparedRequest.EthSign(
preparedRequestData = data,
topic = session.session.topic,
requestId = id,
derivationPath = session.wallet.derivationPath?.rawPath,
),
),
),
)
}
}
fun sendSignedMessage(topic: Topic) {
val activeData = sessions[topic]
val data = activeData?.personalSignData ?: return
scope.launch {
val hash = WalletConnectSdkHelper().signPersonalMessage(
hashToSign = data.hash,
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
type = data.type,
derivationPath = activeData.wallet.derivationPath?.rawPath,
cardId = cardId,
)
.guard {
sessions[topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
data.id,
),
)
return@launch
}
sessions[topic] = activeData.copy(personalSignData = data)
acceptRequest(topic, data.id, hash)
sessions[topic] = activeData.copy(transactionData = null)
}
}
@Suppress("LongMethod", "ComplexMethod")
private fun setListeners(client: WCClient) {
client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
Timber.d("OnSessionRequest: $peer")
val session = client.session
val data = sessions[session?.topic]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
if (data != null && session != null) {
if (!peer.isDappSupported()) {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = session,
error = TapError.WalletConnect.UnsupportedDapp,
),
)
} else {
sessions[session.topic] = data
val sessionData = data.toWalletConnectSession()
sessionData?.let {
store.dispatchOnMain(
WalletConnectAction.ScanCard(
session = sessionData,
chainId = client.chainId?.toIntOrNull(),
),
)
}
}
}
}
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
Timber.d("onSessionUpdate: $update")
val session = client.session
if (session != null && !update.approved) onSessionClosed(session)
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.SendTransaction
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
session = sessionData,
id = id,
type = WcEthTransactionType.EthSendTransaction,
),
)
}
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.SignTransaction
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
session = sessionData,
id = id,
type = WcEthTransactionType.EthSignTransaction,
),
)
}
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.PersonalSign
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandlePersonalSignRequest(
message,
sessionData,
id,
),
)
}
}
client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder ->
}
client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder ->
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.BinanceTransaction.Trade(
id = id,
order = order,
sessionData = sessionData,
),
)
}
}
client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder ->
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.BinanceTransaction.Transfer(
id = id,
order = order,
sessionData = sessionData,
),
)
}
}
client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam ->
// send empty approve request if status is OK
if (order.ok) client.approveRequest(id, "")
}
client.onDisconnect = { code: Int, reason: String ->
val session = client.session
if (session != null) {
onSessionClosed(session)
}
}
client.onWalletChangeNetwork = { id: Long, chainId: Int ->
switchChain(chainId, client)
}
client.onWalletAddNetwork = { id: Long, network: WCAddNetwork ->
// TODO
// In fact this method is used to add a EVM network. It provides RPC url and chain ID.
// Here now it just tries to switch to a EVM network with a provided chain ID.
try {
val chainId = Integer.decode(network.chainIdHex)
switchChain(chainId, client)
} catch (exception: Exception) {
Timber.d("WC add network error: chain could not be parsed")
}
}
}
private fun switchChain(chainId: Int, client: WCClient) {
val blockchain = Blockchain.fromChainId(chainId)
Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
val session = sessions[client.session?.topic]?.toWalletConnectSession()
if (session != null) {
store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
}
}
companion object {
const val WC_SCHEME = "wc"
private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com")
fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null
}
}
internal typealias Topic = String
internal data class WalletConnectActiveData(
val peerId: String,
val remotePeerId: String?,
val client: WCClient,
val session: WCSession,
val peerMeta: WCPeerMeta? = null,
val wallet: WalletForSession,
val transactionData: WcTransactionData? = null,
val personalSignData: WcPersonalSignData? = null,
) {
fun toWalletConnectSession(): WalletConnectSession? {
if (peerMeta == null) return null
return WalletConnectSession(
peerId = peerId,
remotePeerId = remotePeerId,
wallet = wallet,
session = session,
peerMeta = peerMeta,
)
}
}
@Suppress("MagicNumber")
internal class RetryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response = chain.proceed(request)
when (response.code) {
502 -> {
return chain.proceed(request)
}
}
return response
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.trustwallet.walletconnect.models.WCPeerMeta
object WalletConnectNetworkUtils {
fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? {
return when {
peer.url.contains("pancakeswap.finance") -> {
Blockchain.BSC
}
peer.url.contains("optimism") -> {
Blockchain.Optimism
}
chainId != null -> {
Blockchain.fromChainId(chainId)
}
peer.url.contains("matic.network") || peer.name == "Polygon" -> {
Blockchain.Polygon
}
peer.url.contains("binance.org") || peer.name.contains("Binance") -> {
if (peer.icons.firstOrNull()?.contains("testnet") == true) {
Blockchain.BinanceTestnet
} else {
Blockchain.Binance
}
}
peer.name.contains("BSC") -> {
Blockchain.BSC
}
peer.url.contains("honeyswap.1hive.eth.limo") -> {
// Check if something's changed after this bug report:
// https://github.com/1Hive/honeyswap-interface/issues/83
Blockchain.Gnosis
}
else -> {
Blockchain.Ethereum
}
}
}
}

View file

@ -1,105 +0,0 @@
package com.tangem.tap.domain.walletconnect
import android.app.Application
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.session.WCSession
import timber.log.Timber
import java.io.FileNotFoundException
import java.nio.charset.Charset
class WalletConnectRepository(val context: Application) {
private val walletConnectAdapter: JsonAdapter<List<SessionDao>> = MoshiConverter.sdkMoshi.adapter(
Types.newParameterizedType(List::class.java, SessionDao::class.java),
)
fun saveSession(session: WalletConnectSession) {
val sessions = loadSavedSessions() + session
saveSessions(sessions)
}
fun removeSession(session: WCSession) {
val sessions = loadSavedSessions().filterNot { it.session == session }
saveSessions(sessions)
}
fun loadSavedSessions(): List<WalletConnectSession> {
return try {
val json = context.readFileText(FILE_NAME_PREFIX_SESSIONS)
.hexToUtf8()
walletConnectAdapter.fromJson(json)!!.map { it.toSession() }
} catch (e: FileNotFoundException) {
emptyList()
} catch (exception: Exception) {
Timber.w(exception)
emptyList()
}
}
private fun saveSessions(sessions: List<WalletConnectSession>) {
val json = walletConnectAdapter.toJson(sessions.map { SessionDao.fromSession(it) })
.utf8ToHex() // convert to hex to solve problems with saving text with emojis
Timber.e("WC sessions, saving following json: $json")
context.rewriteFile(json, FILE_NAME_PREFIX_SESSIONS)
}
private fun String.utf8ToHex(): String {
return this.toByteArray().toHexString()
}
private fun String.hexToUtf8(): String {
return this.hexToBytes().toString(Charset.defaultCharset())
}
private fun Context.readFileText(fileName: String): String =
this.openFileInput(fileName).bufferedReader().readText()
private fun Context.rewriteFile(content: String, fileName: String) {
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
it.write(content.toByteArray(), 0, content.length)
}
}
companion object {
private const val FILE_NAME_PREFIX_SESSIONS = "wc_sessions"
}
}
@JsonClass(generateAdapter = true)
data class SessionDao(
val peerId: String,
val remotePeerId: String?,
val wallet: WalletForSession,
val session: WCSession,
val peerMeta: WCPeerMeta,
) {
fun toSession(): WalletConnectSession {
return WalletConnectSession(
peerId = peerId,
remotePeerId = remotePeerId,
wallet = wallet,
session = session,
peerMeta = peerMeta,
)
}
companion object {
fun fromSession(session: WalletConnectSession): SessionDao {
return SessionDao(
peerId = session.peerId,
remotePeerId = session.remotePeerId,
wallet = session.wallet,
session = session.session,
peerMeta = session.peerMeta,
)
}
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.*
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
@ -15,13 +16,10 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder
import com.tangem.tap.domain.walletconnect2.domain.TransactionType
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
@ -227,11 +225,11 @@ class WalletConnectSdkHelper {
}
fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade {
return BnbHelper.createMessageData(data.toWCBinanceTradeOrder())
return BnbHelper.createMessageData(data)
}
fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer {
return BnbHelper.createMessageData(data.toWCBinanceTransferOrder())
return BnbHelper.createMessageData(data)
}
suspend fun signBnbTransaction(

View file

@ -1,39 +0,0 @@
package com.tangem.tap.domain.walletconnect.extensions
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
internal fun WCPeerMeta.isDappSupported(): Boolean {
return !unsupportedDappsList.any { this.url.contains(it) }
}
private val unsupportedDappsList: List<String> = listOf("dydx.exchange")
internal fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction {
return WcEthereumTransaction(
from = from,
to = to,
nonce = nonce,
gasPrice = gasPrice,
maxFeePerGas = maxFeePerGas,
maxPriorityFeePerGas = maxPriorityFeePerGas,
gas = gas,
gasLimit = gasLimit,
value = value,
data = data,
)
}
internal fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcSignMessage {
return WcSignMessage(
raw = raw,
type = when (type) {
WCEthereumSignMessage.WCSignType.MESSAGE -> WcSignMessage.WCSignType.MESSAGE
WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcSignMessage.WCSignType.PERSONAL_MESSAGE
WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcSignMessage.WCSignType.TYPED_MESSAGE
},
)
}

View file

@ -1,13 +1,13 @@
package com.tangem.tap.domain.walletconnect2.app
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles
internal class TangemWcBlockchainHelper(
private val featureToggles: WalletConnectFeatureToggles,
featureToggles: WalletConnectFeatureToggles,
) : WcBlockchainHelper {
private val supportedNonEvmBlockchains = if (featureToggles.isSolanaTxSignEnabled) {

View file

@ -6,6 +6,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.datasource.di.SdkMoshi
import com.tangem.datasource.files.FileReader
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
@ -35,6 +38,9 @@ internal object WalletConnectInteractorModule {
wcRepository: WalletConnectRepository,
wcSessionsRepository: WalletConnectSessionsRepository,
walletConnectFeatureToggles: WalletConnectFeatureToggles,
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
walletsStateHolder: WalletsStateHolder,
): WalletConnectInteractor {
return WalletConnectInteractor(
handler = WalletConnectEventsHandlerImpl(),
@ -42,7 +48,10 @@ internal object WalletConnectInteractorModule {
sessionsRepository = wcSessionsRepository,
sdkHelper = WalletConnectSdkHelper(),
blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles),
dispatcher = AppCoroutineDispatcherProvider(),
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
walletsStateHolder = walletsStateHolder,
dispatchers = AppCoroutineDispatcherProvider(),
)
}
}

View file

@ -1,26 +1,52 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.models.*
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@Suppress("LargeClass", "LongParameterList")
class WalletConnectInteractor(
private val handler: WalletConnectEventsHandler,
private val walletConnectRepository: WalletConnectRepository,
private val sessionsRepository: WalletConnectSessionsRepository,
private val sdkHelper: WalletConnectSdkHelper,
private val dispatcher: CoroutineDispatcherProvider,
private val dispatchers: CoroutineDispatcherProvider,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val walletsStateHolder: WalletsStateHolder,
val blockchainHelper: WcBlockchainHelper,
) {
private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) {
GetSelectedWalletUseCase(walletsStateHolder)
}
private val wcScope = CoroutineScope(
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("wcScope"),
)
private val listenerScope = CoroutineScope(
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("listenScope"),
)
private val events = walletConnectRepository.events
private val sessions = walletConnectRepository.activeSessions
@ -34,19 +60,56 @@ class WalletConnectInteractor(
sdkHelper = sdkHelper,
)
suspend fun startListening(userWalletId: String, cardId: String?) {
init {
getSelectedWalletUseCase().onRight { userWalletFlow ->
userWalletFlow
.conflate()
.distinctUntilChanged()
.onEach(::initWithWallet)
.flowOn(dispatchers.io)
.launchIn(wcScope)
}
}
private suspend fun initWithWallet(userWallet: UserWallet) {
if (userWallet.isMultiCurrency) {
Timber.d("WalletConnect: initialize and setup networks for ${userWallet.walletId}")
startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet))
subscribeOnCurrenciesUpdates(userWallet)
}
}
private fun subscribeOnCurrenciesUpdates(userWallet: UserWallet) {
currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWallet.walletId)
.conflate()
.distinctUntilChanged()
.onEach { currencies ->
setupUserChains(userWallet, currencies)
}
.flowOn(dispatchers.io)
.launchIn(wcScope)
}
private suspend fun setupUserChains(userWallet: UserWallet, currencies: List<CryptoCurrency>) {
val accounts = getAccountsForWc(
userWallet = userWallet,
networks = currencies.map { it.network },
)
setUserChains(accounts)
}
private suspend fun startListeningWc(userWalletId: String, cardId: String?) {
this.userWalletId = userWalletId
this.cardId = cardId
coroutineScope {
listenerScope.coroutineContext.cancelChildren()
listenerScope.launch {
launch { subscribeToEvents() }
launch { subscribeToSessions() }
walletConnectRepository.updateSessions()
}
}
fun setUserChains(accounts: List<Account>) {
private fun setUserChains(accounts: List<Account>) {
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
.groupBy { account ->
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
@ -106,7 +169,7 @@ class WalletConnectInteractor(
}
}
}
.flowOn(dispatcher.io)
.flowOn(dispatchers.io)
.collect()
}
@ -117,7 +180,7 @@ class WalletConnectInteractor(
val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics)
handler.onListOfSessionsUpdated(filteredSessions)
}
.flowOn(dispatcher.io)
.flowOn(dispatchers.io)
.collect()
}
@ -258,6 +321,38 @@ class WalletConnectInteractor(
return uri.lowercase().startsWith(WC_SCHEME)
}
private fun getCardId(userWallet: UserWallet): String? {
return if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId
} else { // if wallet has backup, any card from wallet can be used to sign
null
}
}
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
val walletManagers = networks.mapNotNull {
val blockchain = Blockchain.fromId(it.id.value)
walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchain,
derivationPath = it.derivationPath.value,
)
}
return walletManagers.mapNotNull {
val wallet = it.wallet
val chainId = blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
}
private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? {
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
}

View file

@ -1,10 +1,10 @@
package com.tangem.tap.features.customtoken.impl.data
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.models.scan.ScanResponse

View file

@ -13,10 +13,15 @@ import androidx.lifecycle.viewModelScope
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.isSupportedInApp
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.*
import com.tangem.domain.common.extensions.canHandleBlockchain
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.features.addCustomToken.CustomCurrency
@ -232,7 +237,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
ifRight = { it.scanResponse },
)
val derivationStyle = scanResponse?.derivationStyleProvider?.getDerivationStyle()
return listOf(defaultNetwork) + Blockchain.values()
return listOf(defaultNetwork) + Blockchain.entries
.filter { blockchain ->
scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver)
?.contains(blockchain) == true && isDerivationPathNotEmpty(derivationStyle, blockchain)
@ -316,7 +321,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
type = DerivationPathSelectorType.CUSTOM,
derivationPath = "",
),
) + Blockchain.values()
) + Blockchain.entries
.filter { blockchain ->
blockchain.isSupportedInApp() && !blockchain.isTestnet()
}

View file

@ -6,11 +6,14 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.rekotlin.Action
/**
@ -19,21 +22,23 @@ import org.rekotlin.Action
internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
val globalState = store.state.globalState
val noteState = store.state.onboardingNoteState
when (action) {
return when (action) {
is OnboardingNoteAction.Balance.Update -> {
val walletManager = if (noteState.walletManager != null) {
noteState.walletManager
} else {
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
val wmFactory = runBlocking {
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
}
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
return false
}
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
walletManager
}
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
val loadedBalance = noteState.walletBalance.copy(
value = balanceAmount.value!!,
@ -42,6 +47,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
error = null,
criticalError = null,
)
walletManager.wallet.setAmount(balanceAmount)
scope.launch {
@ -53,7 +59,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
}
return true
}
else -> false
}
return false
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.common.*
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
@ -18,7 +17,6 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -27,8 +25,6 @@ import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@ -329,8 +325,10 @@ class DetailsMiddleware {
}
private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
// Nothing to change
if (preferencesStorage.shouldSaveAccessCodes == enable) {
if (shouldSaveAccessCodes == enable) {
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
return@launch
}
@ -368,50 +366,6 @@ class DetailsMiddleware {
private suspend fun saveCurrentWallet(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
return if (featureToggles.isGeneralManagerEnabled) {
saveCurrentWalletByNewWay(scanResponse, enableAccessCodesSaving)
} else {
saveCurrentWalletByOldWay(scanResponse, enableAccessCodesSaving)
}
}
private suspend fun saveCurrentWalletByOldWay(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
val userWallet = userWalletsListManager.selectedUserWalletSync
?: scanResponse?.let { UserWalletBuilder(it).build() }
?: return CompletionResult.Failure(
error = TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")),
)
updateUserWalletsListManager(enableUserWalletsSaving = true)
return userWalletsListManager.save(userWallet)
.flatMap {
if (enableAccessCodesSaving) {
saveAccessCodes(scanResponse)
} else {
CompletionResult.Success(Unit)
}
}
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldShowSaveUserWalletScreen = false
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
}
.doOnFailure { error ->
Timber.e(error, "Unable to save user wallet")
}
}
private suspend fun saveCurrentWalletByNewWay(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
@ -429,31 +383,6 @@ class DetailsMiddleware {
}
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
return if (featureToggles.isGeneralManagerEnabled) {
deleteSavedWalletsAndAccessCodesByNewWay()
} else {
deleteSavedWalletsAndAccessCodesByOldWay()
}
}
private suspend fun deleteSavedWalletsAndAccessCodesByOldWay(): CompletionResult<Unit> {
return userWalletsListManager.clear()
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()
updateUserWalletsListManager(enableUserWalletsSaving = false)
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
}
.doOnFailure { error ->
Timber.e(error, "Unable to delete saved wallets")
}
}
private suspend fun deleteSavedWalletsAndAccessCodesByNewWay(): CompletionResult<Unit> {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()
@ -464,12 +393,14 @@ class DetailsMiddleware {
return CompletionResult.Success(Unit)
}
private fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult<Unit> {
private suspend fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult<Unit> {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true)
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true)
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true,
)
return CompletionResult.Success(Unit)
}
@ -479,33 +410,16 @@ class DetailsMiddleware {
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off))
preferencesStorage.shouldSaveAccessCodes = false
store.inject(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false)
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = false)
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = false,
)
}
.doOnFailure { error ->
Timber.e(error, "Unable to delete saved access codes")
}
}
private suspend fun updateUserWalletsListManager(enableUserWalletsSaving: Boolean) {
val manager = if (enableUserWalletsSaving) {
createBiometricsUserWalletsManager() ?: return
} else {
UserWalletsListManager.provideRuntimeImplementation()
}
store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager))
}
private fun createBiometricsUserWalletsManager(): UserWalletsListManager? {
val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard {
Timber.e(IllegalStateException("No activities in foreground"))
return null
}
return UserWalletsListManager.provideBiometricImplementation(context)
}
}
class AccessCodeRecoveryMiddleware {
@ -574,9 +488,8 @@ class DetailsMiddleware {
val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy()
// Update access code policy for access code saving when a card was scanned
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = shouldSaveAccessCodes)
store.inject(DaggerGraphState::scanCardProcessor).scan(
analyticsSource = CoreAnalyticsParam.ScreensSources.Settings,

View file

@ -7,7 +7,6 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
@ -75,7 +74,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
appSettingsState = AppSettingsState(
isBiometricsAvailable = tangemSdkManager.canUseBiometry,
saveWallets = action.shouldSaveUserWallets,
saveAccessCodes = preferencesStorage.shouldSaveAccessCodes,
saveAccessCodes = runBlocking {
store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
},
selectedAppCurrency = store.state.globalState.appCurrency,
selectedThemeMode = runBlocking {
store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull()

View file

@ -1,118 +1,26 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.walletconnect.Topic
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
import com.tangem.wallet.R
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCSession
import org.rekotlin.Action
sealed class WalletConnectAction : Action {
object ResetState : WalletConnectAction()
data class HandleDeepLink(val wcUri: String?) : WalletConnectAction()
data class RestoreSessions(val scanResponse: ScanResponse) : WalletConnectAction()
data class StartWalletConnect(
val copiedUri: String?,
) : WalletConnectAction()
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
object UnsupportedCard : WalletConnectAction()
data class OpenSession(
val wcUri: String,
) : WalletConnectAction()
data class SetNewSessionData(
val newSession: NewWcSessionData,
) : WalletConnectAction()
data class DisconnectSession(val topic: String) : WalletConnectAction()
object RefuseOpeningSession : WalletConnectAction()
data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction()
data class ScanCard(
val session: WalletConnectSession,
val chainId: Int?,
) : WalletConnectAction()
data class ApproveSession(
val session: WCSession,
) : WalletConnectAction() {
data class Success(val session: WalletConnectSession) : WalletConnectAction()
}
data class SwitchBlockchain(
val blockchain: Blockchain?,
val session: WalletConnectSession,
) : WalletConnectAction()
data class SelectNetwork(val session: WalletConnectSession, val networks: List<Blockchain>) : WalletConnectAction()
data class ChooseNetwork(val blockchain: Blockchain) : WalletConnectAction()
data class UpdateBlockchain(
val updatedSession: WalletConnectSession,
) : WalletConnectAction()
data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction()
data class SetSessionsRestored(val sessions: List<WalletConnectSession>) : WalletConnectAction()
data class DisconnectSession(val topic: String, val session: WCSession?) : WalletConnectAction()
data class RemoveSession(val session: WCSession) : WalletConnectAction()
data class HandleTransactionRequest(
val transaction: WCEthereumTransaction,
val session: WalletConnectSession,
val id: Long,
val type: WcEthTransactionType,
) :
WalletConnectAction()
data class HandlePersonalSignRequest(
val message: WCEthereumSignMessage,
val session: WalletConnectSession,
val id: Long,
) : WalletConnectAction()
data class SendTransaction(val topic: Topic) : WalletConnectAction()
data class SignMessage(val topic: Topic) : WalletConnectAction()
data class RejectRequest(val topic: Topic, val id: Long) : WalletConnectAction()
object NotEnoughFunds : WalletConnectAction(), NotificationAction {
override val messageResource = R.string.wallet_connect_create_tx_not_enough_funds
}
object NotifyCameraPermissionIsRequired : WalletConnectAction(), NotificationAction {
override val messageResource = R.string.common_camera_denied_alert_message
}
object BinanceTransaction : WalletConnectAction() {
data class Trade(
val id: Long,
val order: WCBinanceTradeOrder,
val sessionData: WalletConnectSession,
) : WalletConnectAction()
data class Transfer(
val id: Long,
val order: WCBinanceTransferOrder,
val sessionData: WalletConnectSession,
) : WalletConnectAction()
data class Sign(
val id: Long,
val data: ByteArray,
val topic: Topic,
) : WalletConnectAction()
}
data class RejectRequest(val topic: String, val id: Long) : WalletConnectAction()
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
//region WalletConnect 2.0
object ApproveProposal : WalletConnectAction()
object RejectProposal : WalletConnectAction()

View file

@ -1,44 +1,26 @@
package com.tangem.tap.features.details.redux.walletconnect
import androidx.core.os.bundleOf
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.extensions.guard
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletconnect.WalletConnectActions
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
import com.tangem.tap.domain.walletconnect.extensions.toWcEthTransaction
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@ -46,7 +28,6 @@ import timber.log.Timber
@Suppress("LargeClass")
class WalletConnectMiddleware {
private var walletConnectManager = WalletConnectManager()
private val walletConnectInteractor: WalletConnectInteractor
get() = store.inject(DaggerGraphState::walletConnectInteractor)
private val walletConnectRepository: WalletConnectRepository
@ -66,40 +47,18 @@ class WalletConnectMiddleware {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
is WalletConnectActions.New.Initialize -> {
val userWallet = action.userWallet
val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId
} else { // if wallet has backup, any card from wallet can be used to sign
null
}
scope.launch {
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
wcInteractor.startListening(
userWalletId = userWallet.walletId.stringValue,
cardId = cardId,
)
}
}
is WalletConnectActions.New.SetupUserChains -> {
scope.launch {
val userWallet = action.userWallet
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
wcInteractor.setUserChains(getAccountsForWc(wcInteractor, userWallet))
}
}
is WalletConnectAction.ResetState -> walletConnectManager = WalletConnectManager()
is WalletConnectAction.RestoreSessions -> {
walletConnectManager.restoreSessions(action.scanResponse)
}
is WalletConnectAction.HandleDeepLink -> {
if (!action.wcUri.isNullOrBlank()) {
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
is WalletConnectAction.DisconnectSession -> {
walletConnectInteractor.disconnectSession(action.topic)
}
is WalletConnectAction.StartWalletConnect -> {
val uri = action.copiedUri
if (uri != null && isWalletConnectUri(uri)) {
// TODO check
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchOnMain(
@ -112,27 +71,6 @@ class WalletConnectMiddleware {
)
}
}
is WalletConnectAction.SelectNetwork -> {
store.dispatch(
GlobalAction.ShowDialog(
WalletConnectDialog.ChooseNetwork(
session = action.session,
networks = action.networks,
),
),
)
}
is WalletConnectAction.ChooseNetwork -> {
val data = state()?.walletConnectState?.newSessionData ?: return
scope.launch {
prepareWalletManager(
scanResponse = data.scanResponse,
blockchain = action.blockchain,
session = data.session,
walletConnectManager = walletConnectManager,
)
}
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
store.dispatchOnMain(
GlobalAction.ShowDialog(
@ -142,176 +80,15 @@ class WalletConnectMiddleware {
),
)
}
is WalletConnectAction.OpeningSessionTimeout -> {
Timber.e("OpeningSessionTimeout for topic ${action.session.topic}")
// do not show dialog for now, it shows always to user if cannot establish connection
// store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
}
is WalletConnectAction.FailureEstablishingSession -> {
Timber.e("FailureEstablishingSession for topic ${action.session?.topic}")
// disable alerts in release to avoid annoying users
// if (action.error != null) {
// store.dispatch(
// GlobalAction.ShowDialog(
// AppDialog.SimpleOkDialogRes(
// headerId = R.string.common_warning,
// messageId = action.error.messageResource,
// ),
// ),
// )
// }
if (action.session != null) {
walletConnectManager.disconnect(action.session)
}
}
is WalletConnectAction.UnsupportedCard -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
}
is WalletConnectAction.OpenSession -> {
val index = action.wcUri.indexOf("@")
when (action.wcUri[index + 1]) {
'1' -> {
walletConnectManager.connect(wcUri = action.wcUri)
}
'2' -> walletConnectRepository.pair(uri = action.wcUri)
}
}
is WalletConnectAction.RefuseOpeningSession -> {
Timber.e("RefuseOpeningSession")
// do not show for now to avoid anoying users with alert
// store.dispatch(
// GlobalAction.ShowDialog(
// WalletConnectDialog.OpeningSessionRejected,
// ),
// )
}
is WalletConnectAction.ScanCard -> {
val scanResponse = userWalletsListManager.selectedUserWalletSync.guard {
Timber.w("Unable to get selected user wallet for WC session")
return
}
scope.launch(Dispatchers.Main) {
scanCard(scanResponse, action.session, action.chainId)
}
}
is WalletConnectAction.ApproveSession -> {
walletConnectManager.approve(action.session)
}
is WalletConnectAction.DisconnectSession -> {
if (action.session != null) {
walletConnectManager.disconnect(action.session)
} else {
walletConnectInteractor.disconnectSession(action.topic)
}
}
is WalletConnectAction.HandleTransactionRequest -> {
walletConnectManager.handleTransactionRequest(
transaction = action.transaction.toWcEthTransaction(),
session = action.session,
id = action.id,
type = action.type,
)
}
is WalletConnectAction.HandlePersonalSignRequest -> {
walletConnectManager.handlePersonalSignRequest(
message = action.message,
session = action.session,
id = action.id,
)
}
is WalletConnectAction.RejectRequest -> {
walletConnectManager.rejectRequest(action.topic, action.id)
walletConnectInteractor.cancelRequest(action.topic, action.id)
}
is WalletConnectAction.SendTransaction -> {
walletConnectManager.completeTransaction(action.topic)
}
is WalletConnectAction.SignMessage -> {
walletConnectManager.sendSignedMessage(action.topic)
}
is WalletConnectAction.BinanceTransaction.Trade -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
WcPreparedRequest.BnbTransaction(
preparedRequestData = BnbData(
data = messageData,
topic = action.sessionData.session.topic,
requestId = action.id,
dAppName = action.sessionData.peerMeta.name,
),
topic = action.sessionData.session.topic,
requestId = action.id,
derivationPath = action.sessionData.wallet.derivationPath?.rawPath,
),
),
),
)
}
is WalletConnectAction.BinanceTransaction.Transfer -> {
val messageData = BnbHelper.createMessageData(action.order)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.BnbTransactionDialog(
WcPreparedRequest.BnbTransaction(
preparedRequestData = BnbData(
data = messageData,
topic = action.sessionData.session.topic,
requestId = action.id,
dAppName = action.sessionData.peerMeta.name,
),
topic = action.sessionData.session.topic,
requestId = action.id,
derivationPath = action.sessionData.wallet.derivationPath?.rawPath,
),
),
),
)
}
is WalletConnectAction.BinanceTransaction.Sign -> {
walletConnectManager.signBnb(
id = action.id,
data = action.data,
topic = action.topic,
)
}
is WalletConnectAction.SwitchBlockchain -> {
if (action.session.wallet.derivationStyle == DerivationStyle.LEGACY) {
Timber.d("Cannot switch chains on AC01/AC02 wallets")
return
}
val blockchain = action.blockchain.guard {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
return
}
scope.launch {
val walletManager = getWalletManager(
wallet = action.session.wallet,
blockchain = blockchain,
).guard {
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.AddNetwork(listOf(blockchain.fullName)),
),
)
return@launch
}
val updatedWallet = action.session.wallet.copy(
walletPublicKey = walletManager.wallet.publicKey.seedKey,
derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
derivationPath = walletManager.wallet.publicKey.derivationPath,
blockchain = action.blockchain,
)
val updatedSession = action.session.copy(wallet = updatedWallet)
store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
}
}
is WalletConnectAction.UpdateBlockchain -> {
walletConnectManager.updateBlockchain(action.updatedSession)
}
is WalletConnectAction.ApproveProposal -> {
scope.launch {
val accounts = getWalletManagers()
@ -402,135 +179,7 @@ class WalletConnectMiddleware {
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
}
private suspend fun scanCard(userWallet: UserWallet, session: WalletConnectSession, chainId: Int?) {
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
chainId = chainId,
peer = session.peerMeta,
).guard {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
return
}
handleScanResponse(userWallet, session, blockchain)
}
private suspend fun getAvailableEvmBlockchains(userWalletId: UserWalletId): List<Blockchain> {
val currenciesRepository = store.inject(DaggerGraphState::currenciesRepository)
return currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
.asSequence()
.filterIsInstance<CryptoCurrency.Coin>()
.filterNot { it.isCustom }
.mapNotNull { Blockchain.fromNetworkId(it.network.id.value) }
.filter { it.isEvm() }
.toList()
}
private suspend fun prepareWalletManager(
scanResponse: ScanResponse,
blockchain: Blockchain,
session: WalletConnectSession,
walletConnectManager: WalletConnectManager,
) {
val walletManager = getWalletManager(session.wallet, blockchain).guard {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.AddNetwork(listOf(blockchain.fullName)),
),
)
return
}
val wallet = walletManager.wallet
val derivedKey =
if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
null
} else {
walletManager.wallet.publicKey.blockchainKey
}
val walletForSession = WalletForSession(
walletPublicKey = wallet.publicKey.seedKey,
derivedPublicKey = derivedKey,
derivationPath = wallet.publicKey.derivationPath,
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
blockchain = wallet.blockchain,
)
withMainContext {
val updatedSession = session.copy(wallet = walletForSession)
walletConnectManager.updateSession(updatedSession)
store.dispatch(WalletConnectAction.ApproveSession(session.session))
}
}
private suspend fun handleScanResponse(
userWallet: UserWallet,
session: WalletConnectSession,
blockchain: Blockchain,
) {
val scanResponse = userWallet.scanResponse
if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return
}
val updatedSession = session.copy(wallet = session.wallet.copy(blockchain = blockchain))
store.dispatch(
WalletConnectAction.SetNewSessionData(
NewWcSessionData(updatedSession, scanResponse, blockchain),
),
)
val blockchains = if (blockchain.isEvm()) {
getAvailableEvmBlockchains(userWallet.walletId)
} else {
emptyList()
}
store.dispatch(
GlobalAction.ShowDialog(
WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
),
)
}
private suspend fun getWalletManager(wallet: WalletForSession, blockchain: Blockchain): WalletManager? {
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
Blockchain.EthereumTestnet
} else {
blockchain
}
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null
val derivation = blockchainToMake.derivationPath(
style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
)?.rawPath
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
return walletManagerFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchainToMake,
derivationPath = derivation,
)
}
private fun isWalletConnectUri(uri: String): Boolean {
return WalletConnectManager.isCorrectWcUri(uri) || walletConnectInteractor.isWalletConnectUri(uri)
}
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor, userWallet: UserWallet): List<Account> {
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId).mapNotNull {
val wallet = it.wallet
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
return walletConnectInteractor.isWalletConnectUri(uri)
}
}

View file

@ -7,36 +7,9 @@ object WalletConnectReducer {
if (action !is WalletConnectAction) return state
return when (action) {
is WalletConnectAction.ResetState -> return WalletConnectState()
is WalletConnectAction.ApproveSession.Success -> {
state.copy(
loading = false,
sessions = state.sessions + action.session,
)
}
is WalletConnectAction.OpenSession -> {
state.copy(loading = true)
}
is WalletConnectAction.SetNewSessionData -> {
state.copy(newSessionData = action.newSession)
}
is WalletConnectAction.SetSessionsRestored -> state.copy(
sessions = action.sessions,
)
is WalletConnectAction.RemoveSession -> {
val sessions =
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
state.copy(sessions = sessions)
}
is WalletConnectAction.UnsupportedCard,
is WalletConnectAction.RefuseOpeningSession,
is WalletConnectAction.OpeningSessionTimeout,
is WalletConnectAction.FailureEstablishingSession,
-> state.copy(loading = false)
is WalletConnectAction.UpdateBlockchain -> state.copy(
sessions = state.sessions
.filterNot { it.peerId == action.updatedSession.peerId } + action.updatedSession,
)
is WalletConnectAction.ApproveProposal -> state.copy(loading = true)
is WalletConnectAction.RejectProposal,
is WalletConnectAction.SessionEstablished,

View file

@ -14,12 +14,9 @@ import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.session.WCSession
data class WalletConnectState(
val loading: Boolean = false,
val sessions: List<WalletConnectSession> = listOf(),
val wc2Sessions: List<WcSessionForScreen> = listOf(),
val newSessionData: NewWcSessionData? = null,
)
@ -34,8 +31,6 @@ data class WalletConnectSession(
val peerId: String,
val remotePeerId: String?,
val wallet: WalletForSession,
val session: WCSession,
val peerMeta: WCPeerMeta,
) {
fun getAddress(): String? {
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null

View file

@ -11,7 +11,6 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -42,13 +41,6 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
modifier = modifier,
state = state,
onBackClick = {
if (state.isLoading) {
store.dispatch(
WalletConnectAction.FailureEstablishingSession(
store.state.walletConnectState.newSessionData?.session?.session,
),
)
}
store.dispatch(NavigationAction.PopBackTo())
},
)

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.details.ui.walletconnect
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import kotlinx.collections.immutable.ImmutableList
internal data class WalletConnectScreenState(
@ -13,13 +12,4 @@ internal data class WalletConnectScreenState(
data class WcSessionForScreen(
val description: String,
val sessionId: String,
) {
companion object {
fun fromSession(session: WalletConnectSession): WcSessionForScreen {
return WcSessionForScreen(
description = session.peerMeta.name,
sessionId = session.session.toUri(),
)
}
}
}
)

View file

@ -5,7 +5,6 @@ import arrow.core.getOrElse
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.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
@ -31,28 +30,18 @@ internal class WalletConnectViewModel @Inject constructor(
fun updateState(state: WalletConnectState): WalletConnectScreenState {
Timber.d("WC2 Sessions: ${state.wc2Sessions}")
val sessions = state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions
val sessions = state.wc2Sessions
return WalletConnectScreenState(
sessions.toImmutableList(),
isLoading = state.loading,
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions, state.wc2Sessions) },
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) },
onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) },
)
}
private fun onRemoveSession(
sessionUri: String,
sessions: List<WalletConnectSession>,
wc2sessions: List<WcSessionForScreen>,
) {
sessions
.firstOrNull { it.session.toUri() == sessionUri }
?.let { baseSession ->
store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session.topic, baseSession.session))
return
}
private fun onRemoveSession(sessionUri: String, wc2sessions: List<WcSessionForScreen>) {
wc2sessions.firstOrNull { it.sessionId == sessionUri }?.let {
store.dispatch(WalletConnectAction.DisconnectSession(sessionUri, null))
store.dispatch(WalletConnectAction.DisconnectSession(sessionUri))
}
}
}

View file

@ -1,45 +0,0 @@
package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.store
import com.tangem.wallet.R
object ApproveWcSessionDialog {
fun create(session: WalletConnectSession, networks: List<Blockchain>, context: Context): AlertDialog {
val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" }
val message = context.getString(
R.string.wallet_connect_request_session_start,
session.peerMeta.name,
sessionBlockchain.fullName,
session.peerMeta.url,
)
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
setTitle(context.getString(R.string.wallet_connect_title))
setMessage(message)
setPositiveButton(context.getText(R.string.common_start)) { _, _ ->
store.dispatch(GlobalAction.HideDialog)
store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain))
}
if (networks.size > 1) {
setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ ->
store.dispatch(GlobalAction.HideDialog)
store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks))
}
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
store.dispatch(GlobalAction.HideDialog)
store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
}
setOnCancelListener {
store.dispatch(GlobalAction.HideDialog)
store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
}
}.create()
}
}

View file

@ -42,13 +42,6 @@ object BnbTransactionDialog {
setTitle(context.getString(R.string.wallet_connect_title))
setMessage(fullMessage)
setPositiveButton(positiveButtonTitle) { _, _ ->
store.dispatch(
WalletConnectAction.BinanceTransaction.Sign(
id = preparedData.requestId,
data = data.data,
topic = preparedData.topic,
),
)
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->

View file

@ -1,35 +0,0 @@
package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.store
import com.tangem.wallet.R
object ChooseNetworkDialog {
fun create(session: WalletConnectSession, networks: List<Blockchain>, context: Context): AlertDialog {
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog)
.setTitle(context.getString(R.string.wallet_connect_select_network))
.setNegativeButton(context.getString(R.string.common_cancel)) { _, _ ->
store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
}
.setOnDismissListener {
store.dispatch(GlobalAction.HideDialog)
}
.setSingleChoiceItems(networks.map { it.fullName }.toTypedArray(), 0) { _, which ->
networks.getOrNull(which)?.let { selectedBlockchain ->
store.dispatch(
WalletConnectAction.ChooseNetwork(
blockchain = selectedBlockchain,
),
)
store.dispatch(GlobalAction.HideDialog)
}
}
.create()
}
}

View file

@ -17,7 +17,6 @@ object PersonalSignDialog {
setTitle(context.getString(R.string.wallet_connect_title))
setMessage(message)
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
store.dispatch(WalletConnectAction.SignMessage(data.topic))
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->

View file

@ -32,11 +32,9 @@ object TransactionDialog {
setMessage(message)
setPositiveButton(positiveButtonTitle) { _, _ ->
if (data.isEnoughFundsToSend) {
store.dispatch(WalletConnectAction.SendTransaction(data.topic))
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
} else {
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
store.dispatch(WalletConnectAction.NotEnoughFunds)
}
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->

View file

@ -21,7 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.resources.C
import com.tangem.core.ui.test.TestTags
import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtons
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
@ -57,7 +57,7 @@ fun StoriesScreen(
}
StoriesScreenContent(
modifier = Modifier.fillMaxSize().testTag(C.Tag.STORIES_SCREEN),
modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN),
config = StoriesScreenContentConfig(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.resources.C
import com.tangem.core.ui.test.TestTags
import com.tangem.wallet.R
@Composable
@ -33,7 +33,7 @@ internal fun HomeButtons(
ScanCardButton(
modifier = Modifier
.weight(weight = 1f)
.testTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON),
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
showProgress = btnScanStateInProgress,
onClick = onScanButtonClick,
)
@ -41,7 +41,7 @@ internal fun HomeButtons(
OrderCardButton(
modifier = Modifier
.weight(weight = 1f)
.testTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON),
.testTag(TestTags.STORIES_SCREEN_ORDER_BUTTON),
onClick = onShopButtonClick,
)
}

View file

@ -19,7 +19,6 @@ import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
@ -77,8 +76,10 @@ private fun handleHomeAction(action: Action) {
}
private suspend fun readCard() {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
store.inject(DaggerGraphState::scanCardProcessor).scan(

View file

@ -3,7 +3,6 @@ package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.removePrefixOrNull
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.intentHandler.IntentHandler
import com.tangem.tap.store
@ -20,7 +19,7 @@ class WalletConnectLinkIntentHandler : IntentHandler {
val scheme = intent.scheme ?: return false
val wcUri = when (scheme) {
WalletConnectManager.WC_SCHEME -> intentData.toString()
WC_SCHEME -> intentData.toString()
TANGEM_SCHEME -> intentData.toString().removePrefixOrNull(TANGEM_WC_PREFIX)
else -> null
}
@ -40,8 +39,9 @@ class WalletConnectLinkIntentHandler : IntentHandler {
}
private companion object {
private const val TANGEM_SCHEME = "tangem"
private const val TANGEM_WC_PREFIX = "tangem://wc?uri="
private const val DEFAULT_CHARSET_NAME = "UTF-8"
const val TANGEM_SCHEME = "tangem"
const val TANGEM_WC_PREFIX = "tangem://wc?uri="
const val DEFAULT_CHARSET_NAME = "UTF-8"
const val WC_SCHEME = "wc"
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
@ -11,6 +12,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -26,6 +28,8 @@ internal class MainViewModel @Inject constructor(
private val reduxNavController: ReduxNavController,
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
private val blockchainSDKFactory: BlockchainSDKFactory,
private val dispatchers: CoroutineDispatcherProvider,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents {
@ -39,7 +43,17 @@ internal class MainViewModel @Inject constructor(
val state: StateFlow<MainScreenState> = stateHolder.stateFlow
var isSplashScreenShown: Boolean = true
private set
init {
viewModelScope.launch(dispatchers.main) {
blockchainSDKFactory.init()
isSplashScreenShown = false
}
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
updateAppCurrencies()
observeFlips()
displayBalancesHidingStatusToast()

View file

@ -79,6 +79,8 @@ object OnboardingHelper {
) {
Analytics.setContext(scanResponse)
scope.launch {
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
when {
// When should save user wallets, then save card without navigate to save wallet screen
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
@ -90,16 +92,11 @@ object OnboardingHelper {
),
)
val toggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (toggles.isGeneralManagerEnabled) {
store.dispatchWithMain(SaveWalletAction.SaveWalletAfterBackup(hasBackupError))
} else {
store.dispatchWithMain(SaveWalletAction.Save)
}
}
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
// then open save wallet screen
tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> {
tangemSdkManager.canUseBiometry && settingsRepository.shouldShowSaveUserWalletScreen() -> {
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
delay(timeMillis = 1_200)

View file

@ -23,12 +23,14 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
@ -120,8 +122,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
val walletManager = if (noteState.walletManager != null) {
noteState.walletManager
} else {
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
val wmFactory = runBlocking {
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
}
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
val message = "Loading cancelled. Cause: wallet manager didn't created"
val customError = TapError.CustomError(message)
store.dispatchErrorNotification(customError)

View file

@ -3,7 +3,7 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.Message
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.TwinCardsManager

View file

@ -36,6 +36,7 @@ import com.tangem.tap.userWalletsListManager
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
@ -244,8 +245,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
val walletManager = if (twinCardsState.walletManager != null) {
twinCardsState.walletManager
} else {
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val walletManager = wmFactory.makePrimaryWalletManager(getScanResponse()).guard {
val wmFactory = runBlocking {
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
}
val walletManager = wmFactory?.makePrimaryWalletManager(getScanResponse()).guard {
val message = "Loading cancelled. Cause: wallet manager didn't created"
val customError = TapError.CustomError(message)
store.dispatchErrorNotification(customError)

View file

@ -15,7 +15,7 @@ import com.tangem.common.extensions.VoidCallback
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.ShareElement
import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.Artwork

View file

@ -562,7 +562,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
)
},
)
store.dispatchOnMain(GlobalAction.UpdateUserWalletsListManager(userWalletsListManager))
}
val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull {

View file

@ -11,13 +11,11 @@ internal sealed interface SaveWalletAction : Action {
val backupCardsIds: Set<String>?,
) : SaveWalletAction
data object Save : SaveWalletAction {
data object AllowToUseBiometrics : SaveWalletAction {
data object Success : SaveWalletAction
data class Error(val error: TangemError) : SaveWalletAction
}
data object AllowToUseBiometrics : SaveWalletAction
data object Dismiss : SaveWalletAction
data object CloseError : SaveWalletAction
@ -26,7 +24,5 @@ internal sealed interface SaveWalletAction : Action {
data object Cancel : SaveWalletAction
}
data object SaveWalletWasShown : SaveWalletAction
data class SaveWalletAfterBackup(val hasBackupError: Boolean) : SaveWalletAction
}

View file

@ -7,7 +7,6 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -18,8 +17,6 @@ import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -46,16 +43,14 @@ internal class SaveWalletMiddleware {
private fun handleAction(action: SaveWalletAction, state: SaveWalletState) {
when (action) {
is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state)
is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state)
is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics()
is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown()
is SaveWalletAction.Dismiss -> dismiss(state)
is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup(state, action.hasBackupError)
is SaveWalletAction.Save.Success,
is SaveWalletAction.AllowToUseBiometrics.Success,
is SaveWalletAction.AllowToUseBiometrics.Error,
is SaveWalletAction.ProvideBackupInfo,
is SaveWalletAction.CloseError,
is SaveWalletAction.Save.Error,
is SaveWalletAction.EnrollBiometrics,
is SaveWalletAction.EnrollBiometrics.Cancel,
-> Unit
@ -91,82 +86,6 @@ internal class SaveWalletMiddleware {
store.dispatchOnMain(NavigationAction.OpenBiometricsSettings)
}
private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) {
if (tangemSdkManager.needEnrollBiometrics) {
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics)
} else {
saveWallet(state)
}
}
/**
* or from [SaveWalletState.backupInfo] if provided from
* [com.tangem.tap.features.onboarding.OnboardingHelper.trySaveWalletAndNavigateToWalletScreen]
*
* If saved user's wallet was selected then pop back to [AppScreen.Wallet]
* or navigate to [AppScreen.WalletSelector] otherwise
*
* TODO: Update that logic after onboarding and backup features refactoring
* */
private fun saveWallet(state: SaveWalletState) {
val scanResponse = state.backupInfo?.scanResponse
?: store.state.globalState.scanResponse
?: return
if (state.backupInfo != null) {
// TODO: Remove after onboarding refactoring
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On))
} else {
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
}
scope.launch {
val userWallet = userWalletsListManager.selectedUserWalletSync
?: UserWalletBuilder(scanResponse)
.backupCardsIds(state.backupInfo?.backupCardsIds)
.build()
?: return@launch
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (!featureToggles.isGeneralManagerEnabled) {
provideLockableUserWalletsListManagerIfNot()
}
val isFirstSavedWallet = !userWalletsListManager.hasUserWallets
saveAccessCodeIfNeeded(accessCode = state.backupInfo?.accessCode, cardsInWallet = userWallet.cardsInWallet)
.flatMap {
// Save wallet only at first time (SaveWalletBottomSheet).
// Otherwise (Example, add new wallet in Details) userWalletsListManager.wallets subscribers will
// receive useless updates.
// See: OnboardingHelper.trySaveWalletAndNavigateToWalletScreen()
if (isFirstSavedWallet) {
userWalletsListManager.save(userWallet, canOverride = true)
} else {
CompletionResult.Success(Unit)
}
}
.doOnFailure { error ->
store.dispatchWithMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
// Enable saving access codes only if this is the first time user save the wallet
if (isFirstSavedWallet) {
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
store.dispatchOnMain(SaveWalletAction.Save.Success)
store.navigateToWallet()
}
}.saveIn(saveWalletJobHolder)
}
private fun allowToUseBiometrics(state: SaveWalletState) {
if (tangemSdkManager.needEnrollBiometrics) {
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics)
@ -188,7 +107,9 @@ internal class SaveWalletMiddleware {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
val error = IllegalStateException("No selected user wallet")
Timber.e(error, "Unable to save user wallet")
store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error)))
store.dispatchWithMain(
SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)),
)
return@launch
}
@ -198,29 +119,17 @@ internal class SaveWalletMiddleware {
private suspend fun handleSuccessAllowing(userWallet: UserWallet) {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true)
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
store.dispatchWithMain(SaveWalletAction.Save.Success)
store.dispatchWithMain(SaveWalletAction.AllowToUseBiometrics.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
}
private suspend fun provideLockableUserWalletsListManagerIfNot() {
if (store.state.globalState.userWalletsListManager?.isLockable() == true) return
val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard {
val error = IllegalStateException("No activities in foreground")
Timber.e(error)
store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error)))
return
}
val manager = UserWalletsListManager.provideBiometricImplementation(context)
store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager))
}
private fun dismiss(state: SaveWalletState) {
if (state.backupInfo != null) {
// TODO: Remove after onboarding refactoring
@ -230,10 +139,6 @@ internal class SaveWalletMiddleware {
}
}
private fun saveWalletWasShown() {
preferencesStorage.shouldShowSaveUserWalletScreen = false
}
private suspend fun saveAccessCodeIfNeeded(
accessCode: String?,
cardsInWallet: Set<String>,

View file

@ -21,14 +21,13 @@ internal object SaveWalletReducer {
backupCardsIds = action.backupCardsIds,
),
)
is SaveWalletAction.Save,
is SaveWalletAction.AllowToUseBiometrics,
-> state.copy(isSaveInProgress = true)
is SaveWalletAction.Save.Error -> state.copy(
is SaveWalletAction.AllowToUseBiometrics.Error -> state.copy(
error = action.error,
isSaveInProgress = false,
)
is SaveWalletAction.Save.Success -> state.copy(
is SaveWalletAction.AllowToUseBiometrics.Success -> state.copy(
backupInfo = null,
isSaveInProgress = false,
)
@ -47,7 +46,6 @@ internal object SaveWalletReducer {
needEnrollBiometrics = false,
isSaveInProgress = false,
)
is SaveWalletAction.SaveWalletWasShown,
is SaveWalletAction.SaveWalletAfterBackup,
-> state
}

View file

@ -1,9 +1,10 @@
package com.tangem.tap.features.saveWallet.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.details.ui.cardsettings.TextReference
@ -11,34 +12,36 @@ import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
import com.tangem.tap.store
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@HiltViewModel
internal class SaveWalletViewModel @Inject constructor(
private val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase,
dispatchers: AppCoroutineDispatcherProvider,
) : ViewModel(), StoreSubscriber<SaveWalletState> {
private val stateInternal = MutableStateFlow(SaveWalletScreenState())
val state: StateFlow<SaveWalletScreenState> = stateInternal
init {
subscribeToStoreChanges()
store.dispatchOnMain(SaveWalletAction.SaveWalletWasShown)
viewModelScope.launch(dispatchers.main) {
setSaveWalletScreenShownUseCase()
}
}
fun saveWallet() {
analyticsEventHandler.send(WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
store.dispatch(SaveWalletAction.AllowToUseBiometrics)
} else {
store.dispatch(SaveWalletAction.Save)
}
}
fun cancelOrClose() {

View file

@ -12,13 +12,13 @@ import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactio
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.common.core.TangemSdkError
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.minimalAmount
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.legacy.TradeCryptoAction

View file

@ -3,10 +3,10 @@ package com.tangem.tap.features.tokens.impl.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.tap.features.tokens.impl.data.converters.CoinsResponseConverter

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.tokens.impl.data.converters
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.isSupportedInApp
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.isSupportedInApp
import com.tangem.tap.features.tokens.impl.domain.models.Token
import com.tangem.utils.converter.Converter

View file

@ -1,8 +1,8 @@
package com.tangem.tap.features.tokens.impl.data.converters
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.features.tokens.impl.domain.models.Token
import com.tangem.utils.converter.Converter

View file

@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
@ -19,7 +20,6 @@ import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.canHandleBlockchain
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.supportedTokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase

View file

@ -165,9 +165,12 @@ internal class WelcomeMiddleware {
}
private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
store.inject(DaggerGraphState::scanCardProcessor).scan(
analyticsSource = AnalyticsParam.ScreensSources.SignIn,
onSuccess = { scanResponse ->

View file

@ -1,7 +1,7 @@
package com.tangem.tap.network.auth
import com.tangem.common.extensions.toHexString
import com.tangem.lib.auth.AuthProvider
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.tap.proxy.AppStateHolder
internal class DefaultAuthProvider(private val appStateHolder: AppStateHolder) : AuthProvider {

View file

@ -1,9 +1,9 @@
package com.tangem.tap.network.auth.di
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.lib.auth.AppVersionProvider
import com.tangem.lib.auth.AuthProvider
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.tap.network.auth.DefaultAppVersionProvider
import com.tangem.tap.network.auth.DefaultAuthProvider

View file

@ -13,7 +13,7 @@ import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

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