Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-14 16:28:43 +08:00
parent e0eafb4df9
commit bde99e272b
32 changed files with 384 additions and 184 deletions

View file

@ -25,7 +25,7 @@ dependencies {
implementation(project(":domain:legacy"))
implementation(project(":domain:models"))
implementation(project(":domain:core"))
implementation(project(":domain:card"))
implementation(projects.domain.card)
implementation(project(":domain:wallets"))
implementation(project(":domain:wallets:models"))
implementation(projects.domain.tokens)
@ -41,6 +41,7 @@ dependencies {
implementation(project(":libs:crypto"))
implementation(project(":libs:auth"))
implementation(project(":data:source:preferences"))
implementation(projects.data.card)
implementation(projects.data.tokens)
/** Features */

View file

@ -10,10 +10,11 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.card.sdk.CardSdkLifecycleObserver
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -56,7 +57,6 @@ import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
lateinit var tangemSdkManager: TangemSdkManager
lateinit var backupService: BackupService
internal var lockUserWalletsTimer: LockUserWalletsTimer? = null
@ -88,7 +88,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lateinit var testerRouter: TesterRouter
@Inject
lateinit var injectedTangemSdk: TangemSdk
lateinit var cardSdkLifecycleObserver: CardSdkLifecycleObserver
@Inject
lateinit var cardSdkConfigRepository: CardSdkConfigRepository
@Inject
lateinit var injectedTangemSdkManager: TangemSdkManager
@ -122,11 +125,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
systemActions()
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
tangemSdk = injectedTangemSdk
cardSdkLifecycleObserver.onCreate(context = this)
tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager
appStateHolder.tangemSdk = tangemSdk
backupService = BackupService.init(tangemSdk, this)
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
initUserWalletsListManager()
@ -144,10 +147,37 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
walletRouter = walletRouter,
walletConnectInteractor = walletConnectInteractor,
tokenDetailsRouter = tokenDetailsRouter,
cardSdkConfigRepository = cardSdkConfigRepository,
),
)
}
override fun onStart() {
super.onStart()
dialogManager.onStart(this)
}
override fun onResume() {
super.onResume()
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
navigateToInitialScreenIfNeededOnResume(intent)
}
override fun onStop() {
notificationsHandler = null
dialogManager.onStop()
super.onStop()
}
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
cardSdkLifecycleObserver.onDestroy()
super.onDestroy()
}
private fun initIntentHandlers() {
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider))
@ -183,13 +213,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
override fun onResume() {
super.onResume()
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
navigateToInitialScreenIfNeededOnResume(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
scope.launch {
@ -197,23 +220,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
}
override fun onStart() {
super.onStart()
dialogManager.onStart(this)
}
override fun onStop() {
notificationsHandler = null
dialogManager.onStop()
super.onStop()
}
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
super.onDestroy()
}
override fun showSnackbar(text: Int, buttonTitle: Int?, action: View.OnClickListener?) {
if (snackbar != null) return

View file

@ -3,7 +3,8 @@ package com.tangem.tap.common.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import org.rekotlin.Middleware
class AccessCodeRequestPolicyMiddleware {
@ -19,9 +20,8 @@ class AccessCodeRequestPolicyMiddleware {
}
private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) {
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
scanResponse.card.isAccessCodeSet,
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet,
)
}
}

View file

@ -1,42 +1,43 @@
package com.tangem.tap.di
import android.content.Context
import androidx.fragment.app.FragmentActivity
import com.tangem.TangemSdk
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.sdk.extensions.initWithBiometrics
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.userTokensRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(ActivityComponent::class)
@InstallIn(SingletonComponent::class)
internal object ActivityModule {
@Provides
@ActivityScoped
fun provideTangemSdk(@ActivityContext context: Context): TangemSdk {
return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config)
@Singleton
fun provideTangemSdkManager(
@ApplicationContext context: Context,
cardSdkConfigRepository: CardSdkConfigRepository,
): TangemSdkManager {
return TangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources)
}
@Provides
@ActivityScoped
fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager {
return TangemSdkManager(tangemSdk, context)
}
@Provides
@ActivityScoped
fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase {
@Singleton
fun provideScanCardUseCase(
tangemSdkManager: TangemSdkManager,
cardSdkConfigRepository: CardSdkConfigRepository,
): ScanCardUseCase {
return ScanCardUseCase(
tangemSdk = tangemSdk,
scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager),
cardSdkConfigRepository = cardSdkConfigRepository,
scanCardRepository = DefaultScanCardRepository(
userTokensRepository = userTokensRepository,
tangemSdkManager = tangemSdkManager,
),
)
}
}

View file

@ -1,27 +1,25 @@
package com.tangem.tap.domain
import android.content.Context
import android.content.res.Resources
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.*
import com.tangem.common.biometric.BiometricManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.tap.common.analytics.events.Basic
@ -38,7 +36,10 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
@Suppress("TooManyFunctions")
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources) {
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
private val userCodeRepository by lazy {
UserCodeRepository(
@ -63,7 +64,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse> {
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
runnable = ScanProductTask(
card = null,
@ -80,7 +81,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body)),
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
}
@ -92,7 +93,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
is CompletionResult.Success -> runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body)),
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
@ -111,7 +112,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
CreateWalletAndRescanTask(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)),
initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
.map { CardDTO(it) }
}
@ -127,7 +128,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
runnable = ResetToFactorySettingsTask(),
cardId = cardId,
initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)),
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
)
.map { CardDTO(it) }
}
@ -154,7 +155,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)),
initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)),
)
}
@ -162,7 +163,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)),
initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)),
)
}
@ -170,15 +171,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
)
}
suspend fun checkUserCodes(cardId: String?): CompletionResult<CheckUserCodesResponse> {
return runTaskAsyncReturnOnMain(
CheckUserCodesCommand(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
}
@ -186,7 +179,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
}
@ -197,7 +190,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository),
cardId = cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
.map { CardDTO(it) }
}
@ -233,31 +226,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return context.getString(stringResId, *formatArgs)
}
fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) {
tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
} else {
UserCodeRequestPolicy.Default
}
}
fun useBiometricsForAccessCode(): Boolean {
val policy = tangemSdk.config.userCodeRequestPolicy
return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode
}
companion object {
val config = Config(
linkedTerminal = true,
allowUntrustedCards = true,
filter = CardFilter(
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52),
),
)
return resources.getString(stringResId, *formatArgs)
}
}

View file

@ -36,8 +36,8 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.tap.tangemSdkManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.*
import timber.log.Timber
@ -163,7 +163,10 @@ class WalletConnectSdkHelper {
private suspend fun sendTransaction(data: WcTransactionData, cardId: String?): String? {
val result = (data.walletManager as TransactionSender).send(
transactionData = data.transaction,
signer = CommonSigner(tangemSdk, cardId),
signer = CommonSigner(
tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk,
cardId = cardId,
),
)
return when (result) {
SimpleResult.Success -> {

View file

@ -372,9 +372,9 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldSaveAccessCodes = true
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = scanResponse?.card?.isAccessCodeSet == true,
)
store.state.daggerGraphState
.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true)
return CompletionResult.Success(Unit)
}
@ -385,9 +385,9 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off))
preferencesStorage.shouldSaveAccessCodes = false
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = false,
)
store.state.daggerGraphState
.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false)
}
.doOnFailure { error ->
Timber.e(error, "Unable to delete saved access codes")

View file

@ -9,7 +9,6 @@ import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.events.Shop
@ -26,7 +25,11 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.signin.redux.SignInAction
import com.tangem.tap.preferencesStorage
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.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
@ -74,8 +77,8 @@ private fun handleHomeAction(action: Action) {
private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch {
delay(timeMillis = 200)
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(

View file

@ -30,9 +30,9 @@ import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.toCurrencies
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
object OnboardingWalletMiddleware {
@ -43,8 +43,8 @@ private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state
{ next ->
{ action ->
when (action) {
is OnboardingWallet2Action -> handleWallet2Action(action, state)
else -> handleWalletAction(action, state, dispatch)
is OnboardingWallet2Action -> handleWallet2Action(action)
else -> handleWalletAction(action)
}
next(action)
}
@ -52,7 +52,7 @@ private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state
}
@Suppress("LongMethod", "ComplexMethod")
private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: DispatchFunction) {
private fun handleWalletAction(action: Action) {
if (action !is OnboardingWalletAction) return
val globalState = store.state.globalState
@ -178,7 +178,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
}
@Suppress("LongMethod", "ComplexMethod")
private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) {
private fun handleWallet2Action(action: OnboardingWallet2Action) {
val globalState = store.state.globalState
val onboardingManager = globalState.onboardingState.onboardingManager
@ -329,7 +329,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
is BackupAction.AddBackupCard -> {
backupService.addBackupCard { result ->
backupService.skipCompatibilityChecks = false
tangemSdk.config.filter.cardIdFilter = null
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk
.config.filter.cardIdFilter = null
when (result) {
is CompletionResult.Success -> {
store.dispatchOnMain(BackupAction.AddBackupCard.Success)

View file

@ -22,6 +22,7 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.isLockable
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
@ -110,9 +111,10 @@ internal class SaveWalletMiddleware {
// Enable saving access codes only if this is the first time user save the wallet
if (isFirstSavedWallet) {
preferencesStorage.shouldSaveAccessCodes = true
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = userWallet.hasAccessCode,
)
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {

View file

@ -7,7 +7,6 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactionExtras
import com.tangem.core.navigation.NavigationAction
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
@ -15,6 +14,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.models.scan.CardDTO
@ -39,6 +39,7 @@ import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -201,6 +202,7 @@ private fun sendTransaction(
return@launch
}
val tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk
val linkedTerminalState = tangemSdk.config.linkedTerminal
if (card.isStart2Coin) {
tangemSdk.config.linkedTerminal = false

View file

@ -72,7 +72,7 @@ class TradeCryptoMiddleware {
}
scope.launch {
exchangeManager.buyErc20TestnetTokens(
buyErc20TestnetTokens(
card = card,
walletManager = walletManager,
token = currency.token,

View file

@ -126,11 +126,13 @@ internal class WalletSelectorMiddleware {
private fun addWallet() = scope.launch {
Analytics.send(MyWallets.Button.ScanNewCard())
val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode()
val cardSdkConfigRepository = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository)
val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy()
// Update access code policy for access code saving when a card was scanned
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(
@ -147,14 +149,14 @@ internal class WalletSelectorMiddleware {
saveUserWalletAndPopBackToWalletScreen(scanResponse)
.doOnFailure { error ->
// Rollback policy if card saving was failed
tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
Timber.e(error, "Unable to save user wallet")
store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error))
}
},
onFailure = { error ->
// Rollback policy if card scanning was failed
tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
Timber.e(error, "Unable to scan card")
store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error))
},

View file

@ -100,8 +100,8 @@ internal class WelcomeMiddleware {
}
private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) {
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(
analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn),

View file

@ -13,8 +13,8 @@ import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
/**
@ -86,11 +86,7 @@ class CurrencyExchangeManager(
}
}
suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
card: CardDTO,
walletManager: EthereumWalletManager,
token: Token,
) {
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, token: Token) {
walletManager.safeUpdate()
val amountToSend = Amount(walletManager.wallet.blockchain)
@ -109,7 +105,7 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
val signer = TangemSigner(
card = card,
tangemSdk = tangemSdk,
tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk,
initialMessage = Message(),
) { signResponse ->
store.dispatch(

View file

@ -1,6 +1,5 @@
package com.tangem.tap.proxy
import com.tangem.TangemSdk
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.NavigationStateHolder
import com.tangem.domain.models.scan.CardDTO
@ -30,7 +29,6 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationState
var userTokensRepository: UserTokensRepository? = null
var mainStore: Store<AppState>? = null
var tangemSdkManager: TangemSdkManager? = null
var tangemSdk: TangemSdk? = null
var walletStoresManager: WalletStoresManager? = null
var appFiatCurrency: FiatCurrency = FiatCurrency.Default

View file

@ -14,6 +14,7 @@ import com.tangem.blockchain.extensions.isNetworkError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.lib.crypto.TransactionManager
@ -24,7 +25,6 @@ import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
@ -34,6 +34,7 @@ import java.math.RoundingMode
class TransactionManagerImpl(
private val appStateHolder: AppStateHolder,
private val analytics: AnalyticsEventHandler,
private val cardSdkConfigRepository: CardSdkConfigRepository,
) : TransactionManager {
override suspend fun sendApproveTransaction(
@ -395,7 +396,7 @@ class TransactionManagerImpl(
val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" }
return TangemSigner(
card = actualCard,
tangemSdk = tangemSdk,
tangemSdk = cardSdkConfigRepository.sdk,
initialMessage = Message(),
) { signResponse ->
appStateHolder.mainStore?.dispatch(

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.di
import androidx.compose.ui.text.intl.Locale
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.TransactionManager
@ -39,8 +40,13 @@ class ProxyModule {
fun provideTransactionManager(
appStateHolder: AppStateHolder,
analytics: AnalyticsEventHandler,
cardSdkConfigRepository: CardSdkConfigRepository,
): TransactionManager {
return TransactionManagerImpl(appStateHolder, analytics)
return TransactionManagerImpl(
appStateHolder = appStateHolder,
analytics = analytics,
cardSdkConfigRepository = cardSdkConfigRepository,
)
}
@Provides

View file

@ -1,6 +1,7 @@
package com.tangem.tap.proxy.redux
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
@ -15,5 +16,6 @@ sealed interface DaggerGraphAction : Action {
val walletRouter: WalletRouter,
val walletConnectInteractor: WalletConnectInteractor,
val tokenDetailsRouter: TokenDetailsRouter,
val cardSdkConfigRepository: CardSdkConfigRepository,
) : DaggerGraphAction
}

View file

@ -18,6 +18,7 @@ object DaggerGraphReducer {
walletRouter = action.walletRouter,
walletConnectInteractor = action.walletConnectInteractor,
tokenDetailsRouter = action.tokenDetailsRouter,
cardSdkConfigRepository = action.cardSdkConfigRepository,
)
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -29,6 +30,7 @@ data class DaggerGraphState(
val tokenDetailsFeatureToggles: TokenDetailsFeatureToggles? = null,
val tokenDetailsRouter: TokenDetailsRouter? = null,
val scanCardProcessor: ScanCardProcessor? = null,
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
) : StateType {
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {

1
data/card/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,21 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.data.card"
}
dependencies {
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.tangem.card.android)
implementation(deps.tangem.card.core)
implementation(projects.domain.card)
implementation(projects.domain.models)
}

View file

@ -0,0 +1,53 @@
package com.tangem.data.card
import com.tangem.TangemSdk
import com.tangem.common.UserCodeType
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.models.scan.ProductType
/**
* Implementation of repository for managing with CardSDK config
*
* @property cardSdkProvider CardSDK instance provider
*
[REDACTED_AUTHOR]
*/
internal class DefaultCardSdkConfigRepository(
private val cardSdkProvider: CardSdkProvider,
) : CardSdkConfigRepository {
@Deprecated("Use CardSdkConfigRepository's methods instead of this property")
override val sdk: TangemSdk
get() = cardSdkProvider.sdk
override fun setAccessCodeRequestPolicy(isBiometricsRequestPolicy: Boolean) {
sdk.config.userCodeRequestPolicy = if (isBiometricsRequestPolicy) {
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
} else {
UserCodeRequestPolicy.Default
}
}
override fun isBiometricsRequestPolicy(): Boolean {
return with(sdk.config.userCodeRequestPolicy) {
this is UserCodeRequestPolicy.AlwaysWithBiometrics && codeType == UserCodeType.AccessCode
}
}
override fun resetCardIdDisplayFormat() {
sdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full
}
override fun updateCardIdDisplayFormat(productType: ProductType) {
sdk.config.cardIdDisplayFormat = when (productType) {
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
ProductType.Note,
ProductType.Wallet,
ProductType.Start2Coin,
-> CardIdDisplayFormat.Full
}
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.data.card.di
import com.tangem.data.card.DefaultCardSdkConfigRepository
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.domain.card.repository.CardSdkConfigRepository
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 CardDataModule {
@Provides
@Singleton
fun provideCardSdkConfigRepository(cardSdkProvider: CardSdkProvider): CardSdkConfigRepository {
return DefaultCardSdkConfigRepository(cardSdkProvider = cardSdkProvider)
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.data.card.di
import com.tangem.data.card.sdk.CardSdkLifecycleObserver
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.data.card.sdk.DefaultCardSdkProvider
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface CardSdkModule {
@Binds
@Singleton
fun provideCardSdkProvider(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkProvider
@Binds
@Singleton
fun providerCardSdkLifecycleObserver(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkLifecycleObserver
}

View file

@ -0,0 +1,17 @@
package com.tangem.data.card.sdk
import android.content.Context
/**
* Lifecycle observer for creating Card SDK instance
*
[REDACTED_AUTHOR]
*/
interface CardSdkLifecycleObserver {
/** Callback of creating activity [context] */
fun onCreate(context: Context)
/** Callback of destroying activity */
fun onDestroy()
}

View file

@ -0,0 +1,14 @@
package com.tangem.data.card.sdk
import com.tangem.TangemSdk
/**
* CardSDK instance provider
*
[REDACTED_AUTHOR]
*/
internal interface CardSdkProvider {
/** CardSDK instance */
val sdk: TangemSdk
}

View file

@ -0,0 +1,45 @@
package com.tangem.data.card.sdk
import android.content.Context
import androidx.fragment.app.FragmentActivity
import com.tangem.TangemSdk
import com.tangem.common.CardFilter
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.Config
import com.tangem.sdk.extensions.initWithBiometrics
import javax.inject.Inject
import javax.inject.Singleton
/**
* Implementation of CardSDK instance provider
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, CardSdkLifecycleObserver {
override val sdk: TangemSdk
get() = requireNotNull(value = _sdk) { "Impossible to get the TangemSdk when activity is destroyed" }
private var _sdk: TangemSdk? = null
override fun onCreate(context: Context) {
_sdk = TangemSdk.initWithBiometrics(activity = context as FragmentActivity, config = config)
}
override fun onDestroy() {
_sdk = null
}
private companion object {
val config = Config(
linkedTerminal = true,
allowUntrustedCards = true,
filter = CardFilter(
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52),
),
)
}
}

View file

@ -4,47 +4,46 @@ import arrow.core.Either
import arrow.core.EitherNel
import arrow.core.flatMap
import com.tangem.TangemSdk
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.card.repository.ScanCardRepository
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ChainProcessor
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
/**
* Use case responsible for scanning a card and returning a [ScanResponse] object.
* @property scanCardRepository A repository object implementing [ScanCardRepository] interface.
* @property tangemSdk An instance of [TangemSdk] to configure the display format of the card ID.
* @constructor Create a new instance of [ScanCardUseCase] with the given dependencies.
* Use case responsible for scanning a card and returning a [ScanResponse] object
*
* @property scanCardRepository a repository object implementing [ScanCardRepository] interface
* @property cardSdkConfigRepository an instance of [TangemSdk] to configure the display format of the card ID
*
* @constructor create a new instance of [ScanCardUseCase] with the given dependencies
*/
class ScanCardUseCase(
private val scanCardRepository: ScanCardRepository,
private val tangemSdk: TangemSdk,
private val cardSdkConfigRepository: CardSdkConfigRepository,
) {
/**
* A [ChainProcessor] object to launch the after-scan chains.
*/
/** A [ChainProcessor] object to launch the after-scan chains */
private val scanChainProcessor by lazy {
ChainProcessor<ScanCardException.ChainException, ScanResponse>()
}
/**
* Scan a card and return a [ScanResponse] object.
* @param cardId an optional card ID to scan. If null, can scan any card present.
* Defaults to null.
* @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed.
* Defaults to false.
* @param afterScanChains A list of chains that should be executed after a successful card scan operation.
* Defaults to an empty array.
* @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse].
*
* @param cardId an optional card ID to scan. If null, can scan any card present
* @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed
* @param afterScanChains a list of chains that should be executed after a successful card scan
* operation. Defaults to an empty array
*
* @return a [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse]
*/
suspend operator fun invoke(
cardId: String? = null,
allowRequestAccessCodeFromStorage: Boolean = false,
afterScanChains: List<Chain<ScanCardException.ChainException, ScanResponse>> = emptyList(),
): Either<ScanCardException, ScanResponse> {
resetCardIdDisplayFormat()
cardSdkConfigRepository.resetCardIdDisplayFormat()
scanChainProcessor.addChains(afterScanChains)
return scanCardRepository.scanCard(
@ -52,31 +51,10 @@ class ScanCardUseCase(
allowRequestAccessCodeFromStorage = allowRequestAccessCodeFromStorage,
)
.onRight { scanResponse ->
updateCardIdDisplayFormat(scanResponse.productType)
cardSdkConfigRepository.updateCardIdDisplayFormat(scanResponse.productType)
}
.flatMap { response ->
scanChainProcessor.launchChains(initial = response)
}
}
/**
* Reset the card ID display format to [CardIdDisplayFormat.Full].
*/
private fun resetCardIdDisplayFormat() {
tangemSdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full
}
/**
* Update the card ID display format according to the [ProductType] of the scanned card.
* @param productType The [ProductType] of the scanned card.
*/
private fun updateCardIdDisplayFormat(productType: ProductType) {
tangemSdk.config.cardIdDisplayFormat = when (productType) {
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
ProductType.Note,
ProductType.Wallet,
ProductType.Start2Coin,
-> CardIdDisplayFormat.Full
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.domain.card.repository
import com.tangem.TangemSdk
import com.tangem.domain.models.scan.ProductType
/**
* Repository for managing with CardSDK config
*
[REDACTED_AUTHOR]
*/
interface CardSdkConfigRepository {
/** Tangem SDK instance */
@Deprecated("Use CardSdkConfigRepository's methods instead of this property")
val sdk: TangemSdk
/** Set access code request policy by [isBiometricsRequestPolicy] */
fun setAccessCodeRequestPolicy(isBiometricsRequestPolicy: Boolean)
/** Check if config has biometrics request policy */
fun isBiometricsRequestPolicy(): Boolean
/** Reset the card ID display format to start value */
fun resetCardIdDisplayFormat()
/** Update the card ID display format according to the [productType] of the scanned card */
fun updateCardIdDisplayFormat(productType: ProductType)
}

View file

@ -85,6 +85,7 @@ include(":domain:tokens")
// endregion Domain modules
// region Data modules
include(":data:card")
include(":data:tokens")
include(":data:source:preferences")
// endregion Data modules