Updated on 2026-08-14

This commit is contained in:
Tangem 2021-08-19 16:22:20 +03:00
commit d519fa3207
56 changed files with 627 additions and 519 deletions

View file

@ -76,13 +76,15 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:develop-34'
implementation 'com.tangem:core:fixes_for_app_release-44'
implementation 'com.tangem:sdk:fixes_for_app_release-44'
implementation 'com.tangem:blockchain:develop-36'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-47'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-47'
// WebView
implementation "androidx.browser:browser:1.3.0"
implementation 'com.jakewharton.timber:timber:4.7.1'
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"

View file

@ -2,5 +2,5 @@
"isWalletPayIdEnabled": true,
"isSendingToPayIdEnabled": true,
"isTopUpEnabled": true,
"isCreatingTwinCardsAllowed": false
"isCreatingTwinCardsAllowed": true
}

View file

@ -7,10 +7,7 @@ import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import com.tangem.CardFilter
import com.tangem.Config
import com.tangem.TangemSdk
import com.tangem.commands.common.card.CardType
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.IntentHandler
@ -30,7 +27,6 @@ import kotlinx.coroutines.Job
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.ref.WeakReference
import java.util.*
import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
@ -68,10 +64,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
tangemSdk = TangemSdk.init(
this, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java)))
)
tangemSdkManager = TangemSdkManager(this)
tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
tangemSdkManager = TangemSdkManager(tangemSdk, this)
store.dispatch(WalletConnectAction.RestoreSessions)
}

View file

@ -1,12 +1,11 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
interface AnalyticsHandler {
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: Blockchain? = null)
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null)
fun logCardSdkError(
error: TangemSdkError,
actionToLog: FirebaseAnalyticsHandler.ActionToLog,

View file

@ -5,13 +5,12 @@ import androidx.core.os.bundleOf
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: Blockchain?) {
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: String?) {
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
}
@ -47,18 +46,17 @@ object FirebaseAnalyticsHandler : AnalyticsHandler {
private fun getParamsFromCard(card: Card): Map<AnalyticsParam, String> {
return mapOf(
AnalyticsParam.FIRMWARE to card.firmwareVersion.version,
AnalyticsParam.BATCH_ID to card.cardData?.batchId
AnalyticsParam.FIRMWARE to card.firmwareVersion.stringValue,
AnalyticsParam.BATCH_ID to card.batchId
).filterNotNull()
}
private fun setData(card: Card?, blockchain: Blockchain?): Bundle {
private fun setData(card: Card?, blockchain: String?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
AnalyticsParam.BLOCKCHAIN.param to (blockchain?.currency
?: card.cardData?.blockchainName),
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
AnalyticsParam.BLOCKCHAIN.param to (blockchain),
AnalyticsParam.BATCH_ID.param to card.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.stringValue
)
}

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
import com.tangem.common.core.TangemSdkError
object TangemSdk {
@ -31,17 +31,15 @@ object TangemSdk {
is TangemSdkError.WalletNotFound -> TangemSdkError.WalletNotFound()
is TangemSdkError.AlreadyPersonalized -> TangemSdkError.AlreadyPersonalized()
is TangemSdkError.CannotBeDepersonalized -> TangemSdkError.CannotBeDepersonalized()
is TangemSdkError.Pin1Required -> TangemSdkError.Pin1Required()
is TangemSdkError.AccessCodeRequired -> TangemSdkError.AccessCodeRequired()
is TangemSdkError.CardReadWrongWallet -> TangemSdkError.CardReadWrongWallet()
is TangemSdkError.CardWithMaxZeroWallets -> TangemSdkError.CardWithMaxZeroWallets()
is TangemSdkError.AlreadyCreated -> TangemSdkError.AlreadyCreated()
is TangemSdkError.WalletIndexExceedsMaxValue -> TangemSdkError.WalletIndexExceedsMaxValue()
is TangemSdkError.MaxNumberOfWalletsCreated -> TangemSdkError.MaxNumberOfWalletsCreated()
is TangemSdkError.WalletIndexNotCorrect -> TangemSdkError.WalletIndexNotCorrect()
is TangemSdkError.PurgeWalletProhibited -> TangemSdkError.PurgeWalletProhibited()
is TangemSdkError.Pin1CannotBeChanged -> TangemSdkError.Pin1CannotBeChanged()
is TangemSdkError.Pin2CannotBeChanged -> TangemSdkError.Pin2CannotBeChanged()
is TangemSdkError.Pin1CannotBeDefault -> TangemSdkError.Pin1CannotBeDefault()
is TangemSdkError.AccessCodeCannotBeChanged -> TangemSdkError.AccessCodeCannotBeChanged()
is TangemSdkError.PasscodeCannotBeChanged -> TangemSdkError.PasscodeCannotBeChanged()
is TangemSdkError.AccessCodeCannotBeDefault -> TangemSdkError.AccessCodeCannotBeDefault()
is TangemSdkError.NoRemainingSignatures -> TangemSdkError.NoRemainingSignatures()
is TangemSdkError.EmptyHashes -> TangemSdkError.EmptyHashes()
is TangemSdkError.HashSizeMustBeEqual -> TangemSdkError.HashSizeMustBeEqual()
@ -52,7 +50,7 @@ object TangemSdk {
is TangemSdkError.NotPersonalized -> TangemSdkError.NotPersonalized()
is TangemSdkError.NotActivated -> TangemSdkError.NotActivated()
is TangemSdkError.WalletIsPurged -> TangemSdkError.WalletIsPurged()
is TangemSdkError.Pin2OrCvcRequired -> TangemSdkError.Pin2OrCvcRequired()
is TangemSdkError.PasscodeRequired -> TangemSdkError.PasscodeRequired()
is TangemSdkError.VerificationFailed -> TangemSdkError.VerificationFailed()
is TangemSdkError.DataSizeTooLarge -> TangemSdkError.DataSizeTooLarge()
is TangemSdkError.MissingCounter -> TangemSdkError.MissingCounter()
@ -60,8 +58,8 @@ object TangemSdk {
is TangemSdkError.DataCannotBeWritten -> TangemSdkError.DataCannotBeWritten()
is TangemSdkError.MissingIssuerPubicKey -> TangemSdkError.MissingIssuerPubicKey()
is TangemSdkError.CardVerificationFailed -> TangemSdkError.CardVerificationFailed()
is TangemSdkError.WrongPin1 -> TangemSdkError.WrongPin1()
is TangemSdkError.WrongPin2 -> TangemSdkError.WrongPin2()
is TangemSdkError.WrongAccessCode -> TangemSdkError.WrongAccessCode()
is TangemSdkError.WrongPasscode -> TangemSdkError.WrongPasscode()
is TangemSdkError.UnknownError -> TangemSdkError.UnknownError()
is TangemSdkError.UserCancelled -> TangemSdkError.UserCancelled()
is TangemSdkError.Busy -> TangemSdkError.Busy()
@ -69,8 +67,14 @@ object TangemSdk {
is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber()
is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType()
is TangemSdkError.CardError -> TangemSdkError.CardError()
is TangemSdkError.FirmwareNotSupported -> TangemSdkError.FirmwareNotSupported()
is TangemSdkError.NotSupportedFirmwareVersion -> TangemSdkError.NotSupportedFirmwareVersion()
is TangemSdkError.WalletError -> TangemSdkError.WalletError()
is TangemSdkError.WalletCannotBeCreated -> TangemSdkError.WalletCannotBeCreated()
is TangemSdkError.UnsupportedCurve -> TangemSdkError.UnsupportedCurve()
is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig()
is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError()
is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage)
is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable)
}
}

View file

@ -21,6 +21,9 @@ sealed class GlobalAction : Action {
}
data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()

View file

@ -1,8 +1,8 @@
package com.tangem.tap.common.redux.global
import com.tangem.TangemSdkError
import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager

View file

@ -1,8 +1,6 @@
package com.tangem.tap.common.redux.global
import com.tangem.commands.wallet.WalletIndex
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.details.redux.SecurityOption
import org.rekotlin.Action
fun globalReducer(action: Action, state: AppState): GlobalState {
@ -30,29 +28,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
is GlobalAction.UpdateSecurityOptions -> {
val card = when (action.securityOption) {
SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = true, isPin2Default = true
)
SecurityOption.PassCode -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = true, isPin2Default = false
)
SecurityOption.AccessCode -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = false, isPin2Default = true
)
}
if (card != null) {
globalState.copy(scanNoteResponse = globalState.scanNoteResponse?.copy(card = card))
} else {
globalState
}
}
is GlobalAction.UpdateWalletSignedHashes -> {
val wallet = globalState.scanNoteResponse?.card
?.wallet(WalletIndex.PublicKey(action.walletPublicKey))
?.wallet(action.walletPublicKey)
?.copy(
signedHashes = action.walletSignedHashes,
totalSignedHashes = action.walletSignedHashes,
remainingSignatures = action.remainingSignatures
)
val card = globalState.scanNoteResponse?.card
@ -76,6 +56,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.GetMoonPayUserStatus.Success -> {
globalState.copy(moonPayUserStatus = action.moonPayUserStatus)
}
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
else -> globalState
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tap.common.redux.global
import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
@ -14,10 +13,10 @@ import org.rekotlin.StateType
data class GlobalState(
val scanNoteResponse: ScanNoteResponse? = null,
val cardVerifiedOnline: Boolean = false,
val tapWalletManager: TapWalletManager = TapWalletManager(),
val payIdManager: PayIdManager = PayIdManager(),
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
val tangemService: TangemService = TangemService(),
val configManager: ConfigManager? = null,
val warningManager: WarningMessagesManager? = null,
val feedbackManager: FeedbackManager? = null,

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.network.Result
import com.tangem.common.services.Result
import com.tangem.tap.network.payid.PayIdVerifyService
import com.tangem.tap.network.payid.VerifyPayIdResponse
import kotlinx.coroutines.Dispatchers

View file

@ -1,23 +1,24 @@
package com.tangem.tap.domain
import androidx.activity.ComponentActivity
import com.tangem.*
import com.tangem.commands.CommandResponse
import com.tangem.commands.PinType
import com.tangem.commands.SetPinCommand
import com.tangem.commands.SetPinResponse
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardType
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.commands.wallet.PurgeWalletResponse
import android.content.Context
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.common.CardFilter
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.extensions.calculateSha256
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.common.SuccessResponse
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.Config
import com.tangem.common.core.TangemSdkError
import com.tangem.operations.CommandResponse
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.extensions.getDefaultWalletIndex
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.tasks.ScanNoteTask
@ -25,23 +26,19 @@ import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSdkManager(val activity: ComponentActivity) {
private val tangemSdk = TangemSdk.init(
activity, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java)))
)
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
suspend fun scanNote(
analyticsHandler: AnalyticsHandler, messageRes: Int? = null
analyticsHandler: AnalyticsHandler, messageRes: Int? = null,
): CompletionResult<ScanNoteResponse> {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
val result = runTaskAsyncReturnOnMain(ScanNoteTask(),
initialMessage = Message(
activity.getString(messageRes ?: R.string.initial_message_scan_header)
))
val result = runTaskAsyncReturnOnMain(ScanNoteTask(),
initialMessage = Message(
context.getString(messageRes ?: R.string.initial_message_scan_header)
))
if (result is CompletionResult.Failure) {
(result.error as? TangemSdkError)?.let { error ->
analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan)
@ -52,53 +49,57 @@ class TangemSdkManager(val activity: ComponentActivity) {
suspend fun createWallet(cardId: String?): CompletionResult<Card> {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId,
initialMessage = Message(activity.getString(R.string.initial_message_create_wallet_body)))
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)))
}
suspend fun eraseWallet(cardId: String?): CompletionResult<PurgeWalletResponse> {
return runTaskAsyncReturnOnMain(PurgeWalletCommand(
TangemSdkConstants.getDefaultWalletIndex()),
cardId,
initialMessage = Message(activity.getString(R.string.initial_message_purge_wallet_body)))
suspend fun eraseWallet(card: Card): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
PurgeWalletCommand(card.wallets.first().publicKey),
card.cardId,
initialMessage = Message(context.getString(R.string.initial_message_purge_wallet_body)))
}
suspend fun setPasscode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand(
pinType = PinType.Pin2,
newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
newPin2 = null
), cardId, initialMessage = Message(activity.getString(R.string.initial_message_change_passcode_body)))
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)))
}
suspend fun setAccessCode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand(
pinType = PinType.Pin1,
newPin1 = null,
newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
), cardId, initialMessage = Message(activity.getString(R.string.initial_message_change_access_code_body)))
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)))
}
suspend fun setLongTap(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand(
pinType = PinType.Pin1,
newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
), cardId, initialMessage = Message(activity.getString(R.string.initial_message_tap_header)))
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
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)))
}
suspend fun <T : CommandResponse> runTaskAsync(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult<T> =
withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
}
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
}
}
}
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult<T> {
val result = runTaskAsync(runnable, cardId, initialMessage)
return withContext(Dispatchers.Main) { result }
@ -107,4 +108,12 @@ class TangemSdkManager(val activity: ComponentActivity) {
fun changeDisplayedCardIdNumbersCount(card: Card) {
tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null
}
companion object {
val config = Config(
filter = CardFilter(
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList()
)
)
}
}

View file

@ -3,26 +3,25 @@ package com.tangem.tap.domain
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.commands.SignCommand
import com.tangem.commands.SignResponse
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
import com.tangem.tap.domain.tasks.SignHashTask
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSigner(
private val tangemSdk: TangemSdk,
private val initialMessage: Message,
private val signerCallback: (SignResponse) -> Unit
private val signerCallback: (TangemSignerResponse) -> Unit,
) : TransactionSigner {
override suspend fun sign(
hash: ByteArray,
cardId: String,
walletPublicKey: ByteArray
walletPublicKey: ByteArray,
): CompletionResult<ByteArray> =
suspendCoroutine { continuation ->
val command = SignCommand(arrayOf(hash), WalletIndex.PublicKey(walletPublicKey))
val command = SignHashTask(hash, walletPublicKey)
tangemSdk.startSessionWithRunnable(
runnable = command,
cardId = cardId,
@ -30,8 +29,13 @@ class TangemSigner(
) { result ->
when (result) {
is CompletionResult.Success -> {
signerCallback(result.data)
continuation.resume(CompletionResult.Success(result.data.signatures.first()))
signerCallback(
TangemSignerResponse(
result.data.totalSignedHashes,
result.data.remainingSignatures
)
)
continuation.resume(CompletionResult.Success(result.data.signature))
}
is CompletionResult.Failure ->
continuation.resume(CompletionResult.Failure(result.error))
@ -43,18 +47,23 @@ class TangemSigner(
override suspend fun sign(
hashes: List<ByteArray>,
cardId: String,
walletPublicKey: ByteArray
walletPublicKey: ByteArray,
): CompletionResult<List<ByteArray>> =
suspendCoroutine { continuation ->
val command = SignCommand(hashes.toTypedArray(), WalletIndex.PublicKey(walletPublicKey))
val task = SignHashesTask(hashes, walletPublicKey)
tangemSdk.startSessionWithRunnable(
runnable = command,
runnable = task,
cardId = cardId,
initialMessage = initialMessage,
) { result ->
when (result) {
is CompletionResult.Success -> {
signerCallback(result.data)
signerCallback(
TangemSignerResponse(
result.data.totalSignedHashes,
result.data.remainingSignatures
)
)
continuation.resume(CompletionResult.Success(result.data.signatures))
}
is CompletionResult.Failure ->
@ -62,4 +71,9 @@ class TangemSigner(
}
}
}
}
}
data class TangemSignerResponse(
val totalSignedHashes: Int?,
val remainingSignatures: Int?,
)

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.TangemError
import com.tangem.common.core.TangemError
import com.tangem.wallet.R
interface TapErrors

View file

@ -1,9 +1,8 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.*
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.common.network.Result
import com.tangem.common.card.Card
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.redux.global.FiatCurrencyName
@ -89,7 +88,7 @@ class TapWalletManager {
suspend fun onCardScanned(data: ScanNoteResponse, addAnalyticsEvent: Boolean = false) {
if (addAnalyticsEvent) {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain)
}
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
updateConfigManager(data)
@ -109,7 +108,7 @@ class TapWalletManager {
}
}
val blockchain = data.card.getBlockchain()
val blockchain = data.getBlockchain()
if (blockchain == Blockchain.Ethereum ||
blockchain == Blockchain.EthereumTestnet) {
store.dispatch(TokensAction.LoadCardTokens)
@ -120,12 +119,12 @@ class TapWalletManager {
private fun updateConfigManager(data: ScanNoteResponse) {
val configManager = store.state.globalState.configManager
val blockchain = data.card.getBlockchain()
val blockchain = data.getBlockchain()
if (data.card.isStart2Coin) {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
} else if (blockchain == Blockchain.Bitcoin
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
|| data.walletData?.blockchain == Blockchain.Bitcoin.id) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
} else {
@ -136,25 +135,25 @@ class TapWalletManager {
suspend fun loadData(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
val artworkId = data.verifyResponse?.artworkInfo?.id
store.dispatch(WalletAction.LoadCardInfo(data.card))
when {
data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
data.card.getStatus() == CardStatus.Empty ||
data.card.wallets.isEmpty() ||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
store.dispatch(WalletAction.EmptyWallet)
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
else -> {
val config = store.state.globalState.configManager?.config ?: return@withContext
val blockchain = data.card.getBlockchain()
val blockchain = data.getBlockchain()
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != null && primaryWalletManager != null) {
val primaryToken = data.card.getToken()
val primaryToken = data.getPrimaryToken()
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
if (primaryToken != null) {
@ -173,17 +172,14 @@ class TapWalletManager {
loadMultiWalletData(data.card, blockchain, null)
}
}
store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
val moonPayUserStatus = store.state.globalState.moonPayUserStatus
store.dispatch(WalletAction.LoadWallet(
allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true,
allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true,
))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
store.dispatch(WalletAction.LoadFiatRate())
}
}
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
}
}
@ -236,10 +232,10 @@ class TapWalletManager {
suspend fun reloadData(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
when {
data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
}
data.card.getStatus() == CardStatus.Empty ||
data.card.wallets.isEmpty() ||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
store.dispatch(WalletAction.EmptyWallet)
}

View file

@ -1,10 +1,13 @@
package com.tangem.tap.domain
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.common.card.masks.Product
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.domain.TapWorkarounds.isNote
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.twins.isTwinCard
import java.util.*
object TapWorkarounds {
@ -14,21 +17,37 @@ object TapWorkarounds {
}
val Card.isStart2Coin: Boolean
get() = isStart2CoinIssuer(cardData?.issuerName)
fun Card.isExcluded(): Boolean {
val cardData = this.cardData ?: return false
val productMask = cardData.productMask
val excludedBatch = excludedBatches.contains(cardData.batchId)
val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.uppercase(Locale.ROOT))
val excludedProductMask = (productMask != null && // product mask is on cards v2.30 and later
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard))
return excludedBatch || excludedIssuerName || excludedProductMask
}
get() = isStart2CoinIssuer(issuer.name)
val Card.isTestCard: Boolean
get() = cardData?.batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
fun Card.isExcluded(): Boolean {
val excludedBatch = excludedBatches.contains(batchId)
val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
return excludedBatch || excludedIssuerName
}
fun Card.isNote(): Boolean {
return notesBatches.contains(batchId)
}
fun Card.isMultiCurrencyWallet(): Boolean {
return multiCurrencyWalletsBatches.contains(batchId)
}
val Card.noteCurrency: Blockchain?
get() {
return when (batchId) {
"AB01" -> Blockchain.Bitcoin
"AB02" -> Blockchain.Ethereum
"AB03" -> Blockchain.CardanoShelley
"AB04" -> Blockchain.Dogecoin
"AB05" -> Blockchain.Binance
"AB06" -> Blockchain.XRP
else -> null
}
}
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
@ -44,12 +63,22 @@ object TapWorkarounds {
private val excludedIssuers = listOf(
"TTM BANK"
)
private val notesBatches = listOf(
"AB01",
"AB02",
"AB03",
"AB04",
"AB05",
"AB06",
)
private val multiCurrencyWalletsBatches = listOf("AC01")
}
val Card.isMultiwalletAllowed: Boolean
get() {
return cardData?.productMask?.contains(Product.TwinCard) != true
&& !isStart2Coin
&& (firmwareVersion.major >= 4 ||
return !isTwinCard() && !isStart2Coin && !isNote()
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.domain.configurable.Loader
/**

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.R

View file

@ -1,48 +1,18 @@
package com.tangem.tap.domain.extensions
import com.tangem.FirmwareConstraints
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.wallet.CardWallet
import com.tangem.commands.wallet.WalletStatus
import com.tangem.common.TangemSdkConstants
fun Card.getToken(): Token? {
val symbol = cardData?.tokenSymbol ?: return null
val contractAddress = cardData?.tokenContractAddress ?: return null
val decimals = cardData?.tokenDecimal ?: return null
val blockchain = cardData?.blockchainName?.let { Blockchain.fromId(it) } ?: return null
if (symbol.isBlank() || contractAddress.isBlank()) return null
return Token(symbol, contractAddress, decimals, blockchain)
}
fun Card.getBlockchain(): Blockchain? {
val blockchainName: String = cardData?.blockchainName ?: return null
return Blockchain.fromId(blockchainName)
}
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
fun Card.getSingleWallet(): CardWallet? {
return wallet(TangemSdkConstants.getDefaultWalletIndex())
}
fun Card.getStatus(): CardStatus {
if (firmwareVersion < FirmwareConstraints.AvailabilityVersions.walletData) return status!!
return if (wallets.any { it.status == WalletStatus.Loaded }) {
CardStatus.Loaded
} else {
CardStatus.Empty
}
return wallets.firstOrNull()
}
fun Card.hasSignedHashes(): Boolean {
return wallets.any { it.status == WalletStatus.Loaded && it.signedHashes ?: 0 > 0 }
return wallets.any { it.totalSignedHashes ?: 0 > 0 }
}
fun Card.signedHashesCount(): Int {
return wallets.map { it.signedHashes ?: 0 }.sum()
return wallets.map { it.totalSignedHashes ?: 0 }.sum()
}
val Card.remainingSignatures: Int?

View file

@ -1,8 +0,0 @@
package com.tangem.tap.domain.extensions
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.TangemSdkConstants
fun TangemSdkConstants.Companion.getDefaultWalletIndex(): WalletIndex {
return WalletIndex.Index(oldCardDefaultWalletIndex)
}

View file

@ -3,9 +3,9 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.wallet.CardWallet
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.ScanNoteResponse
@ -54,9 +54,9 @@ fun WalletManagerFactory.makePrimaryWalletManager(
): WalletManager? {
val card = data.card
val blockchain = if (card.isTestCard) {
card.getBlockchain()?.getTestnetVersion()
data.getBlockchain()?.getTestnetVersion()
} else {
card.getBlockchain()
data.getBlockchain()
}
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }

View file

@ -1,28 +1,27 @@
package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.FirmwareConstraints
import com.tangem.TangemSdkError
import com.tangem.commands.common.card.Card
import com.tangem.common.CompletionResult
import com.tangem.tasks.CreateWalletTask
import com.tangem.tasks.PreflightReadCapable
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.PreflightReadTask
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.wallet.CreateWalletCommand
class CreateWalletAndRescanTask : CardSessionRunnable<Card>, PreflightReadCapable {
override val requiresPin2 = false
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val firmwareVerion = session.environment.card?.firmwareVersion
if (firmwareVerion == null) {
val card = session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
}
val task = if (firmwareVerion < FirmwareConstraints.AvailabilityVersions.walletData) {
CreateWalletTask()
val firmwareVersion = card.firmwareVersion
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
CreateWalletCommand(card.supportedCurves.first(), false)
} else {
CreateWalletsTask()
}
@ -30,7 +29,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable<Card>, PreflightReadCapabl
task.run(session) { result ->
when (result) {
is CompletionResult.Success ->
PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}

View file

@ -1,51 +1,47 @@
package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.wallet.CreateWalletCommand
import com.tangem.commands.wallet.WalletConfig
import com.tangem.common.CompletionResult
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.PreflightReadTask
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.wallet.CreateWalletCommand
class CreateWalletsTask(wallets: List<WalletConfig> = emptyList()) : CardSessionRunnable<Card> {
override val requiresPin2 = false
class CreateWalletsTask(curves: List<EllipticCurve> = emptyList()) : CardSessionRunnable<Card> {
val wallets = if (wallets.isEmpty()) {
private val curves = if (curves.isEmpty()) {
listOf(
WalletConfig(null, null, EllipticCurve.Secp256k1, null),
WalletConfig(null, null, EllipticCurve.Ed25519, null),
WalletConfig(null, null, EllipticCurve.Secp256r1, null),
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Secp256r1,
)
} else {
wallets
curves
}
var index = 0
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val walletConfig = wallets[index]
createWallet(walletConfig, session, callback)
val curve = curves[index]
createWallet(curve, session, callback)
}
private fun createWallet(
walletConfig: WalletConfig, session: CardSession,
curve: EllipticCurve, session: CardSession,
callback: (result: CompletionResult<Card>) -> Unit
) {
CreateWalletCommand(
walletConfig, walletIndexValue = walletConfig.curveId!!.toWalletIndex()
).run(session) { result ->
CreateWalletCommand(curve, false).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
if (index == wallets.lastIndex) {
PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
if (index == curves.lastIndex) {
PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
return@run
}
index += 1
createWallet(wallets[index], session, callback)
createWallet(curves[index], session, callback)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))

View file

@ -1,50 +1,67 @@
package com.tangem.tap.domain.tasks
import com.tangem.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.CommandResponse
import com.tangem.commands.ReadIssuerDataCommand
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.verification.VerifyCardCommand
import com.tangem.commands.verification.VerifyCardResponse
import com.tangem.commands.wallet.WalletConfig
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.WalletData
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemError
import com.tangem.common.extensions.toHexString
import com.tangem.operations.CommandResponse
import com.tangem.operations.ScanTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.TapWorkarounds.isMultiCurrencyWallet
import com.tangem.tap.domain.TapWorkarounds.noteCurrency
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.extensions.getStatus
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.tap.store
import com.tangem.tasks.PreflightReadCapable
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.ScanTask
import com.tangem.wallet.R
data class ScanNoteResponse(
val card: Card,
val verifyResponse: VerifyCardResponse? = null,
val secondTwinPublicKey: String? = null,
) : CommandResponse
val card: Card,
val walletData: WalletData?,
val secondTwinPublicKey: String? = null,
) : CommandResponse {
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse>, PreflightReadCapable {
override val requiresPin2 = false
fun getBlockchain(): Blockchain? {
if (card.noteCurrency != null) return card.noteCurrency
val blockchainName: String = walletData?.blockchain ?: return null
return Blockchain.fromId(blockchainName)
}
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
fun getPrimaryToken(): Token? {
val cardToken = walletData?.token ?: return null
return Token(
cardToken.name,
cardToken.symbol,
cardToken.contractAddress,
cardToken.decimals,
Blockchain.fromId(walletData.blockchain)
)
}
}
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse> {
override fun run(
session: CardSession,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
) {
ScanTask().run(session) { result ->
when (result) {
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
is CompletionResult.Success -> {
val card = this.card?.copy(
isPin1Default = result.data.isPin1Default,
isPin2Default = result.data.isPin2Default,
) ?: result.data
val card = this.card ?: result.data
val error = getErrorIfExcludedCard(card)
if (error != null) {
@ -52,39 +69,28 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
return@run
}
verifyCard(card, session, callback)
}
}
}
}
private fun verifyCard(
card: Card, session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
) {
VerifyCardCommand(true).run(session) { verifyResult ->
when (verifyResult) {
is CompletionResult.Success -> {
if (card.isTwinCard()) {
dealWithTwinCard(card, session, verifyResult.data, callback)
dealWithTwinCard(card, session, callback)
} else if (card.firmwareVersion.major >= 4) {
createMissingWalletsIfNeeded(card, session, verifyResult.data, callback)
createMissingWalletsIfNeeded(card, session, callback)
} else {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResult.data)))
callback(CompletionResult.Success(
ScanNoteResponse(card, session.environment.walletData)))
}
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
}
}
}
}
private fun createMissingWalletsIfNeeded(
card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
card: Card, session: CardSession,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
) {
if (card.getStatus() == CardStatus.Empty) {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
val walletData = session.environment.walletData
if (card.wallets.isEmpty()) {
callback(CompletionResult.Success(
ScanNoteResponse(card, walletData)
))
return
}
@ -92,20 +98,14 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
val curvesToCreate = EllipticCurve.values().subtract(curvesPresent)
if (curvesToCreate.isEmpty()) {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
callback(CompletionResult.Success(ScanNoteResponse(card, walletData)))
return
}
val configs = curvesToCreate.map { curve ->
WalletConfig(
isReusable = null, prohibitPurgeWallet = null, curveId = curve,
signingMethods = null
)
}
CreateWalletsTask(configs).run(session) { result ->
CreateWalletsTask(curvesToCreate.toList()).run(session) { result ->
when (result) {
is CompletionResult.Success ->
callback(CompletionResult.Success(ScanNoteResponse(result.data, verifyResponse)))
callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData)))
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
@ -113,8 +113,8 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
}
private fun dealWithTwinCard(
card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
card: Card, session: CardSession,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
) {
ReadIssuerDataCommand().run(session) { readDataResult ->
when (readDataResult) {
@ -125,12 +125,15 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
return@run
}
val verified = TwinCardsManager.verifyTwinPublicKey(
readDataResult.data.issuerData, publicKey
readDataResult.data.issuerData, publicKey
)
if (verified) {
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
callback(CompletionResult.Success(
ScanNoteResponse(card, verifyResponse, twinPublicKey.toHexString())
ScanNoteResponse(
card = card,
walletData = session.environment.walletData,
secondTwinPublicKey = twinPublicKey.toHexString())
))
return@run
} else {
@ -145,15 +148,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
private fun getErrorIfExcludedCard(card: Card): TangemError? {
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
if (card.status == CardStatus.Purged) return TangemSdkError.WalletIsPurged()
if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized()
// Disable new multi-currency HD wallet cards on the old version of the app
if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard()
return null
}
private fun getWalletManagerFactory(): WalletManagerFactory {
val blockchainSdkConfig = store.state.globalState.configManager?.config
?.blockchainSdkConfig ?: BlockchainSdkConfig()
?.blockchainSdkConfig ?: BlockchainSdkConfig()
return WalletManagerFactory(blockchainSdkConfig)
}
}
class UpdateAppToUseThisCard : TangemError {
override val code: Int = 50005
override var customMessage: String = code.toString()
override val messageResId: Int = R.string.error_update_app
}

View file

@ -0,0 +1,36 @@
package com.tangem.tap.domain.tasks
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback
import com.tangem.operations.CommandResponse
import com.tangem.operations.sign.SignHashCommand
class TangemSignHashResponse(
val signature: ByteArray,
val totalSignedHashes: Int?,
val remainingSignatures: Int?,
) : CommandResponse
class SignHashTask(
private val hash: ByteArray,
private val walletPublicKey: ByteArray,
) : CardSessionRunnable<TangemSignHashResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashResponse>) {
SignHashCommand(hash, walletPublicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashResponse(
response.data.signature,
response.data.totalSignedHashes,
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
)))
}
is CompletionResult.Failure ->
callback(CompletionResult.Failure(response.error))
}
}
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.tap.domain.tasks
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback
import com.tangem.operations.CommandResponse
import com.tangem.operations.sign.SignHashesCommand
class TangemSignHashesResponse(
val signatures: List<ByteArray>,
val totalSignedHashes: Int?,
val remainingSignatures: Int?,
) : CommandResponse
class SignHashesTask(
private val hashes: Collection<ByteArray>,
private val walletPublicKey: ByteArray,
) : CardSessionRunnable<TangemSignHashesResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashesResponse>) {
SignHashesCommand(hashes.toTypedArray(), walletPublicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashesResponse(
response.data.signatures,
response.data.totalSignedHashes,
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
)))
}
is CompletionResult.Failure ->
callback(CompletionResult.Failure(response.error))
}
}
}
}

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.termsOfUse
import android.content.res.Resources
import android.net.Uri
import androidx.core.os.ConfigurationCompat
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import java.util.*
/**
@ -13,8 +13,8 @@ class CardTou {
private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
fun getUrl(card: Card): Uri? {
val issuerName = card.cardData?.issuerName ?: return null
if (issuerName.toLowerCase() != "start2coin") return null
val issuerName = card.issuer.name ?: return null
if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null
val baseUrl = "https://app.tangem.com/tou/"
val regionCode = regionCode(card.cardId) ?: "fr"

View file

@ -8,7 +8,7 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.FirmwareVersion
import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.network.createMoshi
@ -127,7 +127,7 @@ class CurrenciesRepository(val context: Application) {
cardFirmware: FirmwareVersion?,
isTestNet: Boolean = false
): List<Blockchain> {
return if (cardFirmware == null || cardFirmware.major < 4) {
return if (cardFirmware == null || cardFirmware < FirmwareVersion.MultiWalletAvailable) {
Blockchain.secp256k1Blockchains(isTestNet)
} else {
Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)

View file

@ -34,9 +34,9 @@ class TopUpManager {
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.walletSignedHashes,
remainingSignatures = signResponse.walletRemainingSignatures,
walletPublicKey = walletManager.wallet.publicKey
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey,
remainingSignatures = signResponse.remainingSignatures
)
)
}

View file

@ -1,34 +1,34 @@
package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.wallet.CreateWalletResponse
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.tap.domain.extensions.getDefaultWalletIndex
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tasks.CreateWalletTask
class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
override val requiresPin2 = false
override fun run(
session: CardSession,
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
) {
if (session.environment.card?.getSingleWallet()?.publicKey != null) {
PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
val publicKey = session.environment.card?.getSingleWallet()?.publicKey
if (publicKey != null) {
PurgeWalletCommand(publicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.changeStatusToEmpty()
CreateWalletTask().run(session) { callback(it) }
session.environment.card = session.environment.card?.setWallets(emptyList())
CreateWalletCommand(EllipticCurve.Secp256k1, false)
.run(session) { callback(it) }
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
}
}
} else {
CreateWalletTask().run(session) { callback(it) }
CreateWalletCommand(EllipticCurve.Secp256k1, false)
.run(session) { callback(it) }
}
}
}

View file

@ -1,31 +1,29 @@
package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.Message
import com.tangem.commands.wallet.CreateWalletResponse
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.domain.extensions.getDefaultWalletIndex
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tasks.CreateWalletTask
class CreateSecondTwinWalletTask(
private val firstPublicKey: String,
private val preparingMessage: Message,
private val creatingWalletMessage: Message
) : CardSessionRunnable<CreateWalletResponse> {
override val requiresPin2 = true
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
if (session.environment.card?.getSingleWallet()?.publicKey != null) {
val publicKey = session.environment.card?.getSingleWallet()?.publicKey
if (publicKey != null) {
session.setInitialMessage(preparingMessage)
PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
PurgeWalletCommand(publicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.changeStatusToEmpty()
session.environment.card = session.environment.card?.setWallets(emptyList())
finishTask(session, callback)
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
@ -38,10 +36,10 @@ class CreateSecondTwinWalletTask(
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
session.setInitialMessage(creatingWalletMessage)
CreateWalletTask().run(session) { result ->
CreateWalletCommand(EllipticCurve.Secp256k1, false).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.changeStatusToLoaded()
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
WriteProtectedIssuerDataTask(
firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys

View file

@ -1,18 +1,17 @@
package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.KeyPair
import com.tangem.common.CompletionResult
import com.tangem.common.KeyPair
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.tasks.ScanNoteTask
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.PreflightReadTask
class FinalizeTwinTask(
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
) : CardSessionRunnable<ScanNoteResponse> {
override val requiresPin2 = true
override fun run(
session: CardSession,
@ -21,7 +20,7 @@ class FinalizeTwinTask(
WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result ->
when (result) {
is CompletionResult.Success ->
PreflightReadTask(PreflightReadSettings.FullCardRead).run(session) { readResult ->
PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
when (readResult) {
is CompletionResult.Success ->
ScanNoteTask(readResult.data).run(session, callback)

View file

@ -1,11 +1,11 @@
package com.tangem.tap.domain.twins
import com.tangem.KeyPair
import com.tangem.Message
import com.tangem.TangemSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import com.tangem.common.KeyPair
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
@ -27,7 +27,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
)
when (response) {
is CompletionResult.Success -> {
currentCardPublicKey = response.data.walletPublicKey.toHexString()
currentCardPublicKey = response.data.wallet.publicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> {
@ -56,7 +56,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
)
when (response) {
is CompletionResult.Success -> {
secondCardPublicKey = response.data.walletPublicKey.toHexString()
secondCardPublicKey = response.data.wallet.publicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> {

View file

@ -1,10 +1,8 @@
package com.tangem.tap.domain.twins
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.common.card.masks.Product
import com.tangem.commands.wallet.WalletStatus
import com.tangem.common.card.Card
import com.tangem.tap.common.extensions.isEven
import com.tangem.tap.domain.twins.TwinsHelper.Companion.getTwinCardNumber
class TwinsHelper {
companion object {
@ -78,19 +76,9 @@ enum class TwinCardNumber(val number: Int) {
}
fun Card.isTwinCard(): Boolean {
return this.cardData?.productMask?.contains(Product.TwinCard) == true
return getTwinCardNumber(cardId) != null
}
fun Card.getTwinCardIdForUser(): String {
return TwinsHelper.getTwinCardIdForUser(this.cardId)
}
fun Card.changeStatusToLoaded(): Card {
val wallets = wallets.map { it.copy(status = WalletStatus.Loaded) }
return copy(status = CardStatus.Loaded, wallets = wallets)
}
fun Card.changeStatusToEmpty(): Card {
val wallets = wallets.map { it.copy(status = WalletStatus.Empty) }
return copy(status = CardStatus.Empty, wallets = wallets)
}

View file

@ -1,49 +1,61 @@
package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.KeyPair
import com.tangem.TangemSdkError
import com.tangem.commands.*
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.KeyPair
import com.tangem.common.SuccessResponse
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.guard
import com.tangem.common.files.FileHashHelper
import com.tangem.tap.domain.extensions.getDefaultWalletIndex
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.operations.issuerAndUserData.ReadIssuerDataResponse
import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.domain.extensions.getSingleWallet
class WriteProtectedIssuerDataTask(
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair
) : CardSessionRunnable<WriteIssuerDataResponse> {
override val requiresPin2 = true
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
) : CardSessionRunnable<SuccessResponse> {
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val hashes = arrayOf(twinPublicKey.calculateSha256())
SignCommand(hashes, TangemSdkConstants.getDefaultWalletIndex()).run(session) { signResult ->
when (signResult) {
is CompletionResult.Success -> {
ReadIssuerDataCommand().run(session) { readResult ->
when (readResult) {
is CompletionResult.Success -> {
writeIssuerData(
twinPublicKey, issuerKeys, signResult.data.signatures[0],
override fun run(
session: CardSession,
callback: (result: CompletionResult<SuccessResponse>) -> Unit,
) {
SignHashCommand(
twinPublicKey.calculateSha256(),
session.environment.card!!.getSingleWallet()!!.publicKey
)
.run(session) { signResult ->
when (signResult) {
is CompletionResult.Success -> {
ReadIssuerDataCommand().run(session) { readResult ->
when (readResult) {
is CompletionResult.Success -> {
writeIssuerData(
twinPublicKey, issuerKeys, signResult.data.signature,
readResult.data, session, callback
)
)
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(
readResult.error))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
}
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
}
}
}
private fun writeIssuerData(
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
readResponse: ReadIssuerDataResponse,
session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>
) -> Unit) {
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
readResponse: ReadIssuerDataResponse,
session: CardSession,
callback: (
result: CompletionResult<SuccessResponse>,
) -> Unit,
) {
val cardId = session.environment.card?.cardId.guard {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
@ -51,11 +63,11 @@ class WriteProtectedIssuerDataTask(
val counter = (readResponse.issuerDataCounter ?: 0) + 1
val data = twinPublicKey + cardSignature
val signedByIssuer = FileHashHelper.prepareHashes(
cardId, data, counter, issuerKeys.privateKey
cardId, data, counter, issuerKeys.privateKey
)
WriteIssuerDataCommand(
data, signedByIssuer.finalizingSignature!!,
counter, issuerKeys.publicKey
data, signedByIssuer.finalizingSignature!!,
counter, issuerKeys.publicKey
).run(session, callback)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.Message
import com.tangem.TangemSdkError
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
@ -11,10 +10,10 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.extensions.*
import com.tangem.commands.SignCommand
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
@ -150,9 +149,9 @@ class WalletConnectSdkHelper {
gasLimit = null
) ?: return null
val command = SignCommand(
hashes = arrayOf(dataToSign.hash),
walletIndex = WalletIndex.PublicKey(data.walletManager.wallet.publicKey)
val command = SignHashCommand(
hash = dataToSign.hash,
walletPublicKey = data.walletManager.wallet.publicKey
)
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message())
return when (result) {
@ -210,13 +209,10 @@ class WalletConnectSdkHelper {
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
val publicKey = wallet.walletPublicKey.hexToBytes()
val command = SignCommand(
arrayOf(hashToSign),
WalletIndex.PublicKey(publicKey))
val command = SignHashCommand(hashToSign, publicKey)
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
is CompletionResult.Success -> {
val hash = result.data.signatures.first()
val hash = result.data.signature
return EthereumUtils.prepareSignedMessageData(
hash, hashToSign, publicKey
)

View file

@ -2,7 +2,8 @@ package com.tangem.tap.features.details.redux
import com.tangem.Message
import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.domain.tasks.ScanNoteResponse
@ -90,6 +91,8 @@ sealed class DetailsAction : Action {
}
sealed class ManageSecurity : DetailsAction() {
data class CheckCurrentSecurityOption(val cardId: String?) : ManageSecurity()
data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity()
object OpenSecurity : ManageSecurity()
data class SelectOption(val option: SecurityOption) : ManageSecurity()
object SaveChanges : ManageSecurity() {

View file

@ -1,9 +1,10 @@
package com.tangem.tap.features.details.redux
import com.tangem.TangemSdkError
import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -83,10 +84,9 @@ class DetailsMiddleware {
store.dispatch(NavigationAction.PopBackTo())
}
is DetailsAction.EraseWallet.Confirm -> {
val card = store.state.detailsState.card ?: return
scope.launch {
val result = tangemSdkManager.eraseWallet(
store.state.detailsState.card?.cardId
)
val result = tangemSdkManager.eraseWallet(card)
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
@ -124,6 +124,21 @@ class DetailsMiddleware {
class ManageSecurityMiddleware {
fun handle(action: DetailsAction.ManageSecurity) {
when (action) {
is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> {
scope.launch {
when (val response = tangemSdkManager.checkUserCodes(action.cardId)) {
is CompletionResult.Success -> {
store.dispatchOnMain(
DetailsAction.ManageSecurity.SetCurrentOption(response.data)
)
store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity)
}
is CompletionResult.Failure -> {
}
}
}
}
is DetailsAction.ManageSecurity.OpenSecurity -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
}

View file

@ -1,8 +1,7 @@
package com.tangem.tap.features.details.redux
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.masks.Settings
import com.tangem.common.card.Card
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.extensions.isWalletDataSupported
@ -47,17 +46,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
}
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState {
val securityOption = when {
action.card.isPin1Default == false -> {
SecurityOption.AccessCode
}
action.card.isPin2Default == false -> {
SecurityOption.PassCode
}
else -> {
SecurityOption.LongTap
}
}
val twinsState = if (action.card.isTwinCard()) {
CreateTwinWalletState(
scanResponse = action.scanNoteResponse,
@ -76,7 +64,6 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
appCurrencyState = AppCurrencyState(
action.fiatCurrencyName
),
securityScreenState = SecurityScreenState(currentOption = securityOption),
createTwinWalletState = twinsState,
cardTermsOfUseUrl = action.cardTou.getUrl(action.card)
)
@ -86,8 +73,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
return when (action) {
DetailsAction.EraseWallet.Check -> {
val notAllowedByCard =
state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
|| state.card?.settingsMask?.contains(Settings.IsReusable) == false
state.card?.settings?.isPermanentWallet == true
|| state.card?.isWalletDataSupported == true
val notEmpty = state.wallets.any {
!it.recentTransactions.toPendingTransactions(it.address).isNullOrEmpty()
@ -142,6 +128,20 @@ private fun handleSecurityAction(
action: DetailsAction.ManageSecurity, state: DetailsState
): DetailsState {
return when (action) {
is DetailsAction.ManageSecurity.SetCurrentOption -> {
val securityOption = when {
action.userCodes.isAccessCodeSet -> {
SecurityOption.AccessCode
}
action.userCodes.isPasscodeSet -> {
SecurityOption.PassCode
}
else -> {
SecurityOption.LongTap
}
}
state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption))
}
is DetailsAction.ManageSecurity.OpenSecurity -> {
if (state.card?.isStart2Coin == true) {
return state.copy(securityScreenState = state.securityScreenState?.copy(
@ -149,13 +149,10 @@ private fun handleSecurityAction(
selectedOption = state.securityScreenState.currentOption
))
}
if (state.card?.isPin2Default == null) {
return state.copy(securityScreenState = state.securityScreenState?.copy(
allowedOptions = EnumSet.noneOf(SecurityOption::class.java)
))
}
val allowedSecurityOptions = prepareAllowedSecurityOptions(state.card)
val allowedSecurityOptions = prepareAllowedSecurityOptions(
state.card, state.securityScreenState?.currentOption
)
state.copy(securityScreenState = state.securityScreenState?.copy(
allowedOptions = allowedSecurityOptions,
selectedOption = state.securityScreenState.currentOption
@ -177,7 +174,6 @@ private fun handleSecurityAction(
is DetailsAction.ManageSecurity.SaveChanges.Success -> {
// Setting options to show only LongTap from now on
state.copy(
card = state.card?.copy(isPin1Default = true, isPin2Default = true),
securityScreenState = state.securityScreenState?.copy(
currentOption = state.securityScreenState.selectedOption,
allowedOptions = EnumSet.of(SecurityOption.LongTap)
@ -188,19 +184,19 @@ private fun handleSecurityAction(
}
}
private fun prepareAllowedSecurityOptions(card: Card): EnumSet<SecurityOption> {
val prohibitDefaultPin = card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
val isDefaultPin1 = card.isPin1Default != false
val isDefaultPin2 = card.isPin2Default != false
private fun prepareAllowedSecurityOptions(
card: Card?, currentSecurityOption: SecurityOption?
): EnumSet<SecurityOption> {
val prohibitDefaultPin = card?.settings?.isRemovingAccessCodeAllowed != true
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) {
if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) {
allowedSecurityOptions.add(SecurityOption.LongTap)
}
if (!isDefaultPin1) {
if (currentSecurityOption == SecurityOption.AccessCode) {
allowedSecurityOptions.add(SecurityOption.AccessCode)
}
if (!isDefaultPin2) {
if (currentSecurityOption == SecurityOption.PassCode) {
allowedSecurityOptions.add(SecurityOption.PassCode)
}
return allowedSecurityOptions
@ -209,6 +205,6 @@ private fun prepareAllowedSecurityOptions(card: Card): EnumSet<SecurityOption> {
private fun Card.toCardInfo(): CardInfo? {
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
val issuer = this.cardData?.issuerName ?: return null
val issuer = this.issuer.name
val signedHashes = this.signedHashesCount()
return CardInfo(cardId, issuer, signedHashes)}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.redux
import android.net.Uri
import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName

View file

@ -1,8 +1,8 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.*
import com.tangem.commands.common.card.Card
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.extensions.toHexString
import com.tangem.tap.*
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler

View file

@ -126,7 +126,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
}
tv_security_title.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.card?.cardId))
}
val currentSecurity = when (state.securityScreenState?.currentOption) {

View file

@ -13,8 +13,7 @@ import androidx.core.content.FileProvider
import com.tangem.Log
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.*
import com.tangem.commands.common.card.Card
import com.tangem.commands.wallet.WalletStatus
import com.tangem.common.card.Card
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.TapWorkarounds
import timber.log.Timber
@ -176,11 +175,10 @@ class AdditionalEmailInfo {
fun setCardInfo(card: Card) {
cardId = card.cardId
cardFirmwareVersion = card.firmwareVersion.version
cardIssuer = card.cardData?.issuerName ?: ""
cardFirmwareVersion = card.firmwareVersion.stringValue
cardIssuer = card.issuer.name
signedHashesCount = card.wallets
.filter { it.status == WalletStatus.Loaded }
.joinToString(";") { "${it.curve?.curve} - ${it.signedHashes}" }
.joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
}
fun setWalletsInfo(walletManagers: List<WalletManager>) {

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.network.Result
import com.tangem.common.services.Result
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.isPayIdSupported

View file

@ -1,12 +1,12 @@
package com.tangem.tap.features.send.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
@ -136,9 +136,9 @@ private fun sendTransaction(
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.walletSignedHashes,
remainingSignatures = signResponse.walletRemainingSignatures,
walletPublicKey = walletManager.wallet.publicKey
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey,
remainingSignatures = signResponse.remainingSignatures
)
)
}
@ -150,7 +150,7 @@ private fun sendTransaction(
FirebaseAnalyticsHandler.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain
blockchain = walletManager.wallet.blockchain.currency
)
dispatch(SendAction.SendSuccess)

View file

@ -3,7 +3,7 @@ package com.tangem.tap.features.wallet.redux
import android.content.Context
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.commands.common.card.Card
import com.tangem.common.card.Card
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.TapError
@ -93,6 +93,8 @@ sealed class WalletAction : Action {
object Failure : WalletAction()
}
class LoadCardInfo(val card: Card) : WalletAction()
data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() {
data class Success(val artwork: Artwork) : WalletAction()
object Failure : WalletAction()

View file

@ -3,13 +3,16 @@ package com.tangem.tap.features.wallet.redux.middlewares
import android.content.Intent
import android.net.Uri
import androidx.core.content.ContextCompat
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.*
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.*
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.shareText
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@ -155,6 +158,32 @@ class WalletMiddleware {
}
}
}
is WalletAction.LoadCardInfo -> {
scope.launch {
val response = OnlineCardVerifier().getCardInfo(
action.card.cardId, action.card.cardPublicKey
)
when (response) {
is Result.Success -> {
store.dispatchOnMain(
WalletAction.SetArtworkId(response.data.artwork?.id)
)
store.dispatchOnMain(
WalletAction.LoadArtwork(action.card, response.data.artwork?.id)
)
store.dispatchOnMain(
GlobalAction.SetIfCardVerifiedOnline(response.data.passed)
)
}
is Result.Failure -> {
store.dispatchOnMain(
GlobalAction.SetIfCardVerifiedOnline(false)
)
}
}
store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded)
}
}
is WalletAction.LoadData -> {
scope.launch {
val scanNoteResponse = globalState?.scanNoteResponse

View file

@ -4,10 +4,8 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.SignatureCountValidator
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardType
import com.tangem.commands.verification.VerifyCardState
import com.tangem.common.extensions.getType
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.isGreaterThan
@ -91,15 +89,13 @@ class WarningsMiddleware {
}
showWarningLowRemainingSignaturesIfNeeded(card)
if (card.getType() != CardType.Release) {
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
addWarningMessage(WarningMessagesManager.devCardWarning())
} else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) }
}
if (card.getType() == CardType.Release) {
if (globalState.scanNoteResponse.verifyResponse?.verificationState ==
VerifyCardState.VerifiedOffline
) {
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) {
if (!globalState.cardVerifiedOnline) {
addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
}
}
@ -161,7 +157,7 @@ class WarningsMiddleware {
val validator = store.state.walletState.walletManagers.firstOrNull()
as? SignatureCountValidator
scope.launch {
val signedHashes = card.getSingleWallet()?.signedHashes ?: 0
val signedHashes = card.getSingleWallet()?.totalSignedHashes ?: 0
val result = validator?.validateSignatureCount(signedHashes)
withContext(Dispatchers.Main) {
when (result) {

View file

@ -2,8 +2,8 @@ package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.network.TangemService
import com.tangem.common.extensions.toHexString
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.FiatCurrencyName
@ -216,21 +216,27 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
is WalletAction.LoadArtwork -> {
val cardId = action.card.cardId
val cardPublicKey = action.card.cardPublicKey?.toHexString()
val artworkUrl = if (cardPublicKey != null && action.artworkId != null) {
TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
} else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) {
Artwork.SERGIO_CARD_URL
} else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
Artwork.MARTA_CARD_URL
} else if (newState.twinCardsState?.cardNumber != null) {
when (newState.twinCardsState?.cardNumber) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
null -> Artwork.DEFAULT_IMG_URL
val cardPublicKey = action.card.cardPublicKey.toHexString()
val artworkUrl = when {
action.artworkId != null -> {
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
}
action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID) -> {
Artwork.SERGIO_CARD_URL
}
action.card.cardId.startsWith(Artwork.MARTA_CARD_ID) -> {
Artwork.MARTA_CARD_URL
}
newState.twinCardsState?.cardNumber != null -> {
when (newState.twinCardsState?.cardNumber) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
null -> Artwork.DEFAULT_IMG_URL
}
}
else -> {
Artwork.DEFAULT_IMG_URL
}
} else {
Artwork.DEFAULT_IMG_URL
}
newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.network.coinmarketcap
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers

View file

@ -1,8 +1,8 @@
package com.tangem.tap.network.moonpay
import com.squareup.moshi.JsonClass
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.network.createRetrofitInstance
class MoonpayService {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.network.payid
import com.squareup.moshi.JsonClass
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.network.createRetrofitInstance
import retrofit2.Retrofit

View file

@ -1,7 +1,7 @@
package com.tangem.tap.network.payid
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.network.createRetrofitInstance
/**

View file

@ -166,4 +166,6 @@ this wallet.
<string name="wallet_dialog_choose_trade_action" translatable="false">Do you want to buy or sell crypto?</string>
<string name="error_update_app" translatable="false">Oops, the current version of the application is not ready to work with this card, please check for updates.</string>
</resources>

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.5.21',
build_gradle: '4.2.2',
build_gradle: '7.0.0',
]

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip