From 812831937c9a96087fadcbaf1d7f31f0b133776e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 May 2021 21:40:31 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- app/build.gradle | 10 +-- .../tap/common/redux/global/GlobalAction.kt | 7 +- .../tap/common/redux/global/GlobalReducer.kt | 23 ++++++- .../com/tangem/tap/domain/TangemSigner.kt | 65 +++++++++++++++++++ .../features/details/redux/DetailsReducer.kt | 5 +- .../features/details/redux/DetailsState.kt | 1 + .../features/details/ui/DetailsFragment.kt | 4 ++ .../send/redux/middlewares/SendMiddleware.kt | 15 ++++- app/src/main/res/layout/fragment_details.xml | 26 +++++++- 9 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/TangemSigner.kt diff --git a/app/build.gradle b/app/build.gradle index 44a13aa801..6674f33d44 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,13 +75,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' -// TODO: change to prod dependencies when they are available -// implementation 'com.tangem:blockchain:1.164.0' -// implementation 'com.tangem:core:1.109.0' -// implementation 'com.tangem:sdk:1.109.0' - implementation 'blockchain-sdk-kotlin:blockchain:tangem-20210416.151414-8' - implementation 'com.tangem:tangem-core:tangem-20210414.185224-2' - implementation 'com.tangem:tangem-sdk:tangem-20210414.185231-2' + implementation 'com.tangem:blockchain:develop-5' + implementation 'com.tangem:core:develop-10' + implementation 'com.tangem:sdk:develop-10' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 2a2a612757..38f45e4ca7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -24,7 +24,12 @@ sealed class GlobalAction : Action { data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() } - data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction() + data class UpdateWalletSignedHashes( + val walletSignedHashes: Int?, + val remainingSignatures: Int?, + val walletPublicKey: ByteArray, + ) : GlobalAction() + data class HideWarningMessage(val warning: WarningMessage) : GlobalAction() data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 7b7e74d399..c7ed09486a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -1,5 +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 @@ -32,13 +33,13 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.UpdateSecurityOptions -> { val card = when (action.securityOption) { SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = true, isPin2Default = true + isPin1Default = true, isPin2Default = true ) SecurityOption.PassCode -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = true, isPin2Default = false + isPin1Default = true, isPin2Default = false ) SecurityOption.AccessCode -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = false, isPin2Default = true + isPin1Default = false, isPin2Default = true ) } if (card != null) { @@ -47,6 +48,22 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState } } + is GlobalAction.UpdateWalletSignedHashes -> { + val wallet = globalState.scanNoteResponse?.card + ?.wallet(WalletIndex.PublicKey(action.walletPublicKey)) + ?.copy( + signedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + ) + val card = globalState.scanNoteResponse?.card + wallet?.let { globalState.scanNoteResponse.card.updateWallet(wallet) } + + if (card != null) { + globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) + } else { + globalState + } + } is GlobalAction.SetFeedbackManager -> { globalState.copy(feedbackManager = action.feedbackManager) } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt new file mode 100644 index 0000000000..7a3556b33a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -0,0 +1,65 @@ +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 kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class TangemSigner( + private val tangemSdk: TangemSdk, + private val initialMessage: Message, + private val signerCallback: (SignResponse) -> Unit +) : TransactionSigner { + + override suspend fun sign( + hash: ByteArray, + cardId: String, + walletPublicKey: ByteArray + ): CompletionResult = + suspendCoroutine { continuation -> + val command = SignCommand(arrayOf(hash), WalletIndex.PublicKey(walletPublicKey)) + tangemSdk.startSessionWithRunnable( + runnable = command, + cardId = cardId, + initialMessage = initialMessage, + ) { result -> + when (result) { + is CompletionResult.Success -> { + signerCallback(result.data) + continuation.resume(CompletionResult.Success(result.data.signatures.first())) + } + is CompletionResult.Failure -> + continuation.resume(CompletionResult.Failure(result.error)) + } + } + } + + + override suspend fun sign( + hashes: List, + cardId: String, + walletPublicKey: ByteArray + ): CompletionResult> = + suspendCoroutine { continuation -> + val command = SignCommand(hashes.toTypedArray(), WalletIndex.PublicKey(walletPublicKey)) + tangemSdk.startSessionWithRunnable( + runnable = command, + cardId = cardId, + initialMessage = initialMessage, + ) { result -> + when (result) { + is CompletionResult.Success -> { + signerCallback(result.data) + continuation.resume(CompletionResult.Success(result.data.signatures)) + } + is CompletionResult.Failure -> + continuation.resume(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 4ec1681b66..a699f2ee45 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -5,6 +5,7 @@ import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.masks.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapWorkarounds +import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.domain.twins.isTwinCard @@ -203,5 +204,5 @@ private fun prepareAllowedSecurityOptions(card: Card): EnumSet { private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.cardData?.issuerName ?: return null - return CardInfo(cardId, issuer) -} \ No newline at end of file + val signedHashes = this.signedHashesCount() + return CardInfo(cardId, issuer, signedHashes)} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 22fca3db45..5ef1033e2f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -26,6 +26,7 @@ data class DetailsState( data class CardInfo( val cardId: String, val issuer: String, + val signedHashes: Int, ) enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index fc793be4cc..895f379b09 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -74,8 +74,12 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.walletSignedHashes, + remainingSignatures = signResponse.walletRemainingSignatures, + walletPublicKey = walletManager.wallet.publicKey + ) + ) + } val result = (walletManager as TransactionSender).send(txData, signer) withContext(Dispatchers.Main) { when (result) { diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 69b8b5cf55..fc33c31d7d 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -90,6 +90,30 @@ app:layout_constraintTop_toBottomOf="@id/tv_card_id" tools:text="Tangem" /> + + + + + app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" /> Date: Wed, 12 May 2021 22:43:31 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../common/redux/global/GlobalMidlleware.kt | 3 ++ .../warningMessage/WarningMessage.kt | 1 + .../warningMessage/WarningMessagesManager.kt | 16 +++++++ .../com/tangem/tap/domain/extensions/Card.kt | 5 +- .../tap/features/wallet/redux/WalletAction.kt | 1 + .../redux/middlewares/WarningsMiddleware.kt | 46 +++++++++++++++++-- .../ui/adapters/WarningMessagesAdapter.kt | 8 ++-- .../main/res/values/strings_untranslated.xml | 2 + 8 files changed, 73 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index dce356f1be..b06dc1b5df 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -53,6 +53,9 @@ val globalMiddleware: Middleware = { dispatch, appState -> is GlobalAction.SendFeedback -> { store.state.globalState.feedbackManager?.send(action.emailData) } + is GlobalAction.UpdateWalletSignedHashes -> { + store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures)) + } } nextDispatch(action) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt index 8494a61786..3c3ad5f5ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt @@ -19,6 +19,7 @@ data class WarningMessage( @StringRes val messageResId: Int? = null, val origin: Origin = Origin.Remote, @StringRes val buttonTextId: Int? = null, + val messageFormatArg: String? = null ) { val blockchainList: List? by lazy { blockchains?.map { Blockchain.fromId(it.toUpperCase()) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 8bde4ae283..59e91a7f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.configurable.warningMessage +import com.tangem.TangemSdkError import com.tangem.blockchain.common.Blockchain import com.tangem.tangem_sdk_new.ui.animation.VoidCallback import com.tangem.tap.common.extensions.containsAny @@ -140,5 +141,20 @@ class WarningMessagesManager( R.string.warning_failed_to_verify_card_message, WarningMessage.Origin.Local ) + + fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage( + title = "", + message = "", + type = WarningMessage.Type.Permanent, + priority = WarningMessage.Priority.Critical, + listOf(WarningMessage.Location.MainScreen), + blockchains = null, + titleResId = R.string.alert_title, + messageResId = R.string.warning_low_signatures_format, + origin = WarningMessage.Origin.Local, + messageFormatArg = remainingSignatures.toString() + ) + + const val REMAINING_SIGNATURES_WARNING = 10 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 4dd07f00f5..eb60189b4f 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -41,4 +41,7 @@ fun Card.hasSignedHashes(): Boolean { fun Card.signedHashesCount(): Int { return getWallets().map { it.signedHashes ?: 0 }.sum() -} \ No newline at end of file +} + +val Card.remainingSignatures: Int? + get() = this.getSingleWallet()?.remainingSignatures \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 18ffaa31b1..405e96be66 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -68,6 +68,7 @@ sealed class WalletAction : Action { object SetNeverToShow : Warnings() object RemindLater : Warnings() } + class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings() } data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 46a7693492..211e5ef2b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -16,6 +16,7 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.extensions.hasSignedHashes +import com.tangem.tap.domain.extensions.remainingSignatures import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.wallet.redux.WalletAction @@ -48,6 +49,17 @@ class WarningsMiddleware { is WalletAction.Warnings.AppRating.SetNeverToShow -> { preferencesStorage.appRatingLaunchObserver.setNeverToShow() } + is WalletAction.Warnings.CheckRemainingSignatures -> { + if (action.remainingSignatures != null && + action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + ) { + addWarningMessage( + warning = + WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), + autoUpdate = true + ) + } + } } } @@ -67,6 +79,7 @@ class WarningsMiddleware { private fun showCardWarningsIfNeeded(globalState: GlobalState?) { globalState?.scanNoteResponse?.card?.let { card -> globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) + showWarningLowRemainingSignaturesIfNeeded(card) if (card.getType() != CardType.Release) { addWarningMessage(WarningMessagesManager.devCardWarning()) } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { @@ -74,7 +87,8 @@ class WarningsMiddleware { } if (card.getType() == CardType.Release) { if (globalState.scanNoteResponse.verifyResponse?.verificationState == - VerifyCardState.VerifiedOffline) { + VerifyCardState.VerifiedOffline + ) { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } @@ -82,8 +96,21 @@ class WarningsMiddleware { } } + private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { + val remainingSignatures = card.remainingSignatures + if (remainingSignatures != null && + remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + ) { + addWarningMessage( + WarningMessagesManager.remainingSignaturesNotEnough( + remainingSignatures + ) + ) + } + } + private fun checkIfWarningNeeded( - card: Card + card: Card ): WarningMessage? { if (card.isTwinCard()) return null @@ -133,9 +160,15 @@ class WarningsMiddleware { } is SimpleResult.Failure -> if (result.error is BlockchainSdkError.SignatureCountNotMatched) { - addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) + addWarningMessage( + WarningMessagesManager.alreadySignedHashesWarning(), + true + ) } else if (signedHashes > 0) { - addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) + addWarningMessage( + WarningMessagesManager.alreadySignedHashesWarning(), + true + ) } } } @@ -153,6 +186,9 @@ class WarningsMiddleware { private fun getWarnings(): List { val warningManager = store.state.globalState.warningManager ?: return emptyList() - return warningManager.getWarnings(WarningMessage.Location.MainScreen, store.state.walletState.blockchains) + return warningManager.getWarnings( + WarningMessage.Location.MainScreen, + store.state.walletState.blockchains + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 80b739b21c..c262bbb35c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -54,11 +54,13 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { } private fun setText(warning: WarningMessage) { - fun getString(resId: Int?, default: String) = - if (resId == null) default else view.getString(resId) + fun getString(resId: Int?, default: String, formatArgs: String? = null) = + if (resId == null) default else view.context.getString(resId, formatArgs) view.tv_title.text = getString(warning.titleResId, warning.title) - view.tv_message.text = getString(warning.messageResId, warning.message) + view.tv_message.text = getString( + resId = warning.messageResId, default = warning.message, formatArgs = warning.messageFormatArg + ) } private fun setBgColor(priority: WarningMessage.Priority) { diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 762001572e..339ccae0b2 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -111,4 +111,6 @@ this wallet. \n\nIt’s perfectly safe in all other respects. \n\nTangem is the only hardware wallet to offer signature count protection. I understand + + There are only %s signatures available on this card. You must withdraw all of your funds.