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

@ -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)
}
store.dispatchWithMain(SaveWalletAction.SaveWalletAfterBackup(hasBackupError))
}
// 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)
}
store.dispatch(SaveWalletAction.AllowToUseBiometrics)
}
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 ->