Updated on 2026-08-14

This commit is contained in:
Tangem 2021-06-29 15:37:45 +03:00
commit 636c714683
42 changed files with 5430 additions and 1027 deletions

View file

@ -50,7 +50,7 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
coreLibraryDesugaringEnabled false
}
packagingOptions {
exclude 'lib/x86_64/darwin/libscrypt.dylib'
@ -77,7 +77,7 @@ 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-26'
implementation 'com.tangem:blockchain:BSK-84_testnets-27'
implementation 'com.tangem:core:fixes_for_app_release-44'
implementation 'com.tangem:sdk:fixes_for_app_release-44'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
[
{
"decimalCount" : 18,
"symbol" : "WEENUS",
"name" : "Weenus",
"contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5"
},
{
"decimalCount" : 18,
"symbol" : "XEENUS",
"name" : "Xeenus",
"contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c"
},
{
"decimalCount" : 8,
"symbol" : "YEENUS",
"name" : "Yeenus",
"contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7"
},
{
"decimalCount" : 0,
"symbol" : "ZEENUS",
"name" : "Zeenus",
"contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e"
}
]

View file

@ -8,4 +8,8 @@ enum class AnalyticsEvent(val event: String) {
APP_RATING_DISMISS("dismiss_rate_app_warning"),
APP_RATING_NEGATIVE("negative_rate_app_feedback"),
APP_RATING_POSITIVE("positive_rate_app_feedback"),
WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
WC_INVALID_REQUEST("wallet_connect_invalid_request"),
WC_NEW_SESSION("wallet_connect_new_session"),
WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
}

View file

@ -1,8 +1,16 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: Blockchain? = null)
fun logCardSdkError(
error: TangemSdkError,
actionToLog: FirebaseAnalyticsHandler.ActionToLog,
parameters: Map<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
card: Card? = null
)
}

View file

@ -3,34 +3,143 @@ package com.tangem.tap.common.analytics
import android.os.Bundle
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.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
Firebase.analytics.logEvent(event.event, setCardData(card))
override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: Blockchain?) {
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
}
fun logException(name: String, throwable: Throwable) {
Firebase.analytics.logEvent(name, bundleOf(
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
"message" to (throwable.message ?: "none"),
"cause_message" to (throwable.cause?.message ?: "none"),
"stack_trace" to throwable.stackTraceToString()
))
}
private fun setCardData(card: Card?): Bundle {
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: ActionToLog,
parameters: Map<AnalyticsParam, String>?,
card: Card?,
) {
if (error is TangemSdkError.UserCancelled) return
val params = parameters?.toMutableMap() ?: mutableMapOf()
if (card != null) params + getParamsFromCard(card)
params[AnalyticsParam.ACTION] = actionToLog.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
params.forEach {
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
}
val cardError = TangemSdk.map(error)
FirebaseCrashlytics.getInstance().recordException(cardError)
}
private fun getParamsFromCard(card: Card): Map<AnalyticsParam, String> {
return mapOf(
AnalyticsParam.FIRMWARE to card.firmwareVersion.version,
AnalyticsParam.BATCH_ID to card.cardData?.batchId
).filterNotNull()
}
private fun setData(card: Card?, blockchain: Blockchain?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName,
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
AnalyticsParam.BLOCKCHAIN.param to (blockchain?.currency
?: card.cardData?.blockchainName),
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
)
}
private enum class AnalyticsParam(val param: String) {
fun logWcEvent(event: WcAnalyticsEvent) {
when (event) {
is WcAnalyticsEvent.Action -> {
Firebase.analytics.logEvent(
AnalyticsEvent.WC_SUCCESS_RESPONSE.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
)
)
}
is WcAnalyticsEvent.Error -> {
mapOf(
AnalyticsParam.WALLET_CONNECT_ACTION to event.action?.name,
AnalyticsParam.ERROR_DESCRIPTION to event.error.message
)
.filterNotNull()
.forEach {
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
}
FirebaseCrashlytics.getInstance().recordException(event.error)
}
is WcAnalyticsEvent.InvalidRequest ->
Firebase.analytics.logEvent(
AnalyticsEvent.WC_INVALID_REQUEST.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
)
)
is WcAnalyticsEvent.Session -> {
val analyticsEvent = when (event.event) {
WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
}
Firebase.analytics.logEvent(
analyticsEvent.event, bundleOf(
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
)
)
}
}
}
enum class AnalyticsParam(val param: String) {
BLOCKCHAIN("blockchain"),
BATCH_ID("batch_id"),
FIRMWARE("firmware"),
ACTION("action"),
ERROR_DESCRIPTION("error_description"),
ERROR_CODE("error_code"),
NEW_SECURITY_OPTION("new_security_option"),
ERROR_KEY("Tangem SDK error key"),
WALLET_CONNECT_ACTION("wallet_connect_action"),
WALLET_CONNECT_REQUEST("wallet_connect_request"),
WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
}
}
enum class ActionToLog(val key: String) {
Scan("tap_scan_task"),
SendTransaction("send_transaction"),
WalletConnectSign("wallet_connect_personal_sign"),
WalletConnectTransaction("wallet_connect_tx_sign"),
ReadPinSettings("read_pin_settings"),
ChangeSecOptions("change_sec_options"),
CreateWallet("create_wallet"),
PurgeWallet("purge_wallet"),
WriteIssuerData("write_issuer_data"),
}
enum class WcSessionEvent { Disconnect, Connect }
sealed class WcAnalyticsEvent {
data class Error(val error: Throwable, val action: WcAction?) :
WcAnalyticsEvent()
data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
data class Action(val action: WcAction) : WcAnalyticsEvent()
data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
}
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
}

View file

@ -0,0 +1,77 @@
package com.tangem.tap.common.analytics
import com.tangem.TangemSdkError
object TangemSdk {
// This mapping is performed to group errors in FirebaseCrashlytics.
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
fun map(error: TangemSdkError): TangemSdkError {
return when (error) {
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
is TangemSdkError.ExtendedLengthNotSupported -> TangemSdkError.ExtendedLengthNotSupported()
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
is TangemSdkError.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand()
is TangemSdkError.InvalidState -> TangemSdkError.InvalidState()
is TangemSdkError.InsNotSupported -> TangemSdkError.InsNotSupported()
is TangemSdkError.InvalidParams -> TangemSdkError.InvalidParams()
is TangemSdkError.NeedEncryption -> TangemSdkError.NeedEncryption()
is TangemSdkError.FileNotFound -> TangemSdkError.FileNotFound()
is TangemSdkError.WalletNotFound -> TangemSdkError.WalletNotFound()
is TangemSdkError.AlreadyPersonalized -> TangemSdkError.AlreadyPersonalized()
is TangemSdkError.CannotBeDepersonalized -> TangemSdkError.CannotBeDepersonalized()
is TangemSdkError.Pin1Required -> TangemSdkError.Pin1Required()
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.NoRemainingSignatures -> TangemSdkError.NoRemainingSignatures()
is TangemSdkError.EmptyHashes -> TangemSdkError.EmptyHashes()
is TangemSdkError.HashSizeMustBeEqual -> TangemSdkError.HashSizeMustBeEqual()
is TangemSdkError.WalletIsNotCreated -> TangemSdkError.WalletIsNotCreated()
is TangemSdkError.SignHashesNotAvailable -> TangemSdkError.SignHashesNotAvailable()
is TangemSdkError.TooManyHashesInOneTransaction -> TangemSdkError.TooManyHashesInOneTransaction()
is TangemSdkError.ExtendedDataSizeTooLarge -> TangemSdkError.ExtendedDataSizeTooLarge()
is TangemSdkError.NotPersonalized -> TangemSdkError.NotPersonalized()
is TangemSdkError.NotActivated -> TangemSdkError.NotActivated()
is TangemSdkError.WalletIsPurged -> TangemSdkError.WalletIsPurged()
is TangemSdkError.Pin2OrCvcRequired -> TangemSdkError.Pin2OrCvcRequired()
is TangemSdkError.VerificationFailed -> TangemSdkError.VerificationFailed()
is TangemSdkError.DataSizeTooLarge -> TangemSdkError.DataSizeTooLarge()
is TangemSdkError.MissingCounter -> TangemSdkError.MissingCounter()
is TangemSdkError.OverwritingDataIsProhibited -> TangemSdkError.OverwritingDataIsProhibited()
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.UnknownError -> TangemSdkError.UnknownError()
is TangemSdkError.UserCancelled -> TangemSdkError.UserCancelled()
is TangemSdkError.Busy -> TangemSdkError.Busy()
is TangemSdkError.MissingPreflightRead -> TangemSdkError.MissingPreflightRead()
is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber()
is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType()
is TangemSdkError.CardError -> TangemSdkError.CardError()
is TangemSdkError.FirmwareNotSupported -> TangemSdkError.FirmwareNotSupported()
is TangemSdkError.WalletError -> TangemSdkError.WalletError()
}
}
}

View file

@ -0,0 +1,4 @@
package com.tangem.tap.common.extensions
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>

View file

@ -1,7 +1,9 @@
package com.tangem.tap.common.extensions
import android.graphics.PorterDuff
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
@ -48,6 +50,7 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = null
textView.text = null
}
if (blockchain.isTestnet()) imageView.tint(R.color.tint)
}
})
}
@ -70,12 +73,20 @@ private fun setOfflineCurrencyImage(
} else {
setBlockchainImage(imageView, textView, blockchain)
}
if (blockchain.isTestnet()) imageView.tint(R.color.tint)
}
fun ImageView.tint(colorRes: Int) {
// val color = ContextCompat.getColor(context, colorRes);
// ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color));
this.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.DARKEN);
}
private fun setBlockchainImage(
imageView: ImageView,
textView: TextView,
blockchain: Blockchain
blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getIconRes())
imageView.colorFilter = null
@ -85,7 +96,7 @@ private fun setBlockchainImage(
private fun setTokenImage(
imageView: ImageView,
textView: TextView,
token: Token
token: Token,
) {
imageView.setImageResource(R.drawable.shape_circle)
imageView.setColorFilter(token.getColor())

View file

@ -37,7 +37,7 @@ fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrenc
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
val fiatValue = rateValue.multiply(this)
return fiatValue.setScale(2, RoundingMode.DOWN)
return fiatValue.setScale(2, RoundingMode.HALF_UP)
}
fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String {

View file

@ -16,6 +16,7 @@ import com.tangem.common.extensions.calculateSha256
import com.tangem.tangem_sdk_new.extensions.init
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
@ -37,10 +38,16 @@ class TangemSdkManager(val activity: ComponentActivity) {
analyticsHandler: AnalyticsHandler, messageRes: Int? = null
): CompletionResult<ScanNoteResponse> {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
return runTaskAsyncReturnOnMain(ScanNoteTask(),
val result = runTaskAsyncReturnOnMain(ScanNoteTask(),
initialMessage = Message(
activity.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)
}
}
return result
}
suspend fun createWallet(cardId: String?): CompletionResult<Card> {

View file

@ -10,6 +10,7 @@ import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.*
import com.tangem.tap.domain.tasks.ScanNoteResponse
@ -96,6 +97,7 @@ class TapWalletManager {
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
if (data.card.isTwinCard()) {
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)

View file

@ -27,7 +27,12 @@ object TapWorkarounds {
}
val Card.isTestCard: Boolean
get() = cardData?.batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val excludedBatches = listOf(
"0027",

View file

@ -1,6 +1,5 @@
package com.tangem.tap.domain.configurable.warningMessage
import android.view.View
import androidx.annotation.StringRes
import com.squareup.moshi.Json
import com.tangem.blockchain.common.Blockchain
@ -46,7 +45,9 @@ data class WarningMessage(
@Json(name = "temporary")
Temporary, // можно скрыть (кнопка ОК)
AppRating
AppRating,
TestCard
}

View file

@ -156,6 +156,18 @@ class WarningMessagesManager(
messageFormatArg = remainingSignatures.toString()
)
fun testCardWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.TestCard,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.warning_testnet_card_message,
WarningMessage.Origin.Local
)
const val REMAINING_SIGNATURES_WARNING = 10
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.EllipticCurve
import java.math.BigDecimal
@ -26,17 +25,4 @@ fun Blockchain.minimalAmount(): BigDecimal {
return 1.toBigDecimal().movePointLeft(decimals())
}
fun Blockchain.getSupportedCurves(): List<EllipticCurve>? {
return when (this) {
Blockchain.Unknown -> null
Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.BitcoinCash, Blockchain.Litecoin,
Blockchain.Ducatus, Blockchain.Ethereum, Blockchain.EthereumTestnet, Blockchain.RSK,
Blockchain.Binance, Blockchain.BinanceTestnet -> listOf(EllipticCurve.Secp256k1)
Blockchain.Tezos, Blockchain.XRP -> listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
Blockchain.Cardano, Blockchain.CardanoShelley, Blockchain.Stellar ->
listOf(EllipticCurve.Ed25519)
else -> listOf(EllipticCurve.Secp256k1)
}
}
private const val NODL = "NODL"

View file

@ -7,6 +7,7 @@ import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.wallet.CardWallet
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.twins.isTwinCard
@ -19,28 +20,44 @@ fun WalletManagerFactory.makeWalletManagerForApp(
val wallet = selectWallet(wallets)
val publicKey = wallet?.publicKey ?: return null
val curveToUse = wallet.curve ?: return null
return makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
return if (card.isTestCard) {
blockchain.getTestnetVersion()?.let {
makeWalletManager(card.cardId, publicKey, it, curveToUse)
}
} else {
makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
}
}
private fun selectWallet(wallets: List<CardWallet>): CardWallet? {
return when (wallets.size) {
0 -> null
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
return when (wallets.size) {
0 -> null
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
}
fun WalletManagerFactory.makeWalletManagersForApp(
card: Card, blockchains: List<Blockchain>,
): List<WalletManager> {
return blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
return if (card.isTestCard) {
blockchains.mapNotNull { blockchain ->
blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) }
}
} else {
blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
}
}
fun WalletManagerFactory.makePrimaryWalletManager(
data: ScanNoteResponse,
): WalletManager? {
val card = data.card
val blockchain = card.getBlockchain()
val blockchain = if (card.isTestCard) {
card.getBlockchain()?.getTestnetVersion()
} else {
card.getBlockchain()
}
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(wallets)

View file

@ -93,42 +93,29 @@ class CurrenciesRepository(val context: Application) {
}
}
fun getPopularTokens(): List<Token> {
val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
fun getPopularTokens(isTestNet: Boolean = false): List<Token> {
val fileName = if (isTestNet) TESTNET_TOKENS_FILE_NAME else POPULAR_TOKENS_FILE_NAME
val json = context.assets.readJsonFileToString(fileName)
return tokensAdapter.fromJson(json)!!.map { it.toToken() }
}
fun getBlockchains(cardFirmware: FirmwareVersion?): List<Blockchain> {
fun getBlockchains(cardFirmware: FirmwareVersion?, isTestNet: Boolean = false): List<Blockchain> {
return if (cardFirmware == null || cardFirmware.major < 4) {
secp256k1Blockchains
Blockchain.secp256k1Blockchains(isTestNet)
} else {
secp256k1Blockchains + ed25519Blockchains
Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)
}
}
companion object {
private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
private const val TESTNET_TOKENS_FILE_NAME = "ethereum_tokens_testnet"
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
fun getFileNameForBlockchains(cardId: String): String =
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
private val secp256k1Blockchains = listOf(
Blockchain.Bitcoin,
Blockchain.BitcoinCash,
Blockchain.Binance,
Blockchain.BSC,
Blockchain.Litecoin,
Blockchain.XRP,
Blockchain.Tezos,
Blockchain.Ethereum,
Blockchain.RSK,
Blockchain.Polygon,
Blockchain.Dogecoin,
)
private val ed25519Blockchains = listOf(Blockchain.CardanoShelley, Blockchain.Stellar)
}
}

View file

@ -1,7 +1,8 @@
package com.tangem.tap.domain
package com.tangem.tap.domain.topup
import android.net.Uri
import android.util.Base64
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
@ -15,10 +16,20 @@ class TopUpHelper {
private const val API_KEY_PATH = "?apiKey="
private const val CURRENCY_PATH = "&currencyCode="
private const val WALLET_ADDRESS_PATH = "&walletAddress="
// private const val REDIRECT_URL_PATH = "&redirectUrl="
// private const val REDIRECT_URL_PATH = "&redirectUrl="
private const val SIGNATURE_PATH = "&signature="
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String, apiKey: String, secretKey: String): String {
fun getUrl(
blockchain: Blockchain?,
cryptoCurrencyName: CryptoCurrencyName,
walletAddress: String,
apiKey: String,
secretKey: String,
): String {
if (blockchain?.isTestnet() == true) {
return blockchain.getTestnetTopUpUrl() ?: ""
}
val originalQuery = API_KEY_PATH + apiKey.urlEncode() +
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
WALLET_ADDRESS_PATH + walletAddress.urlEncode()
@ -41,4 +52,4 @@ class TopUpHelper {
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
}
}
}
}

View file

@ -0,0 +1,45 @@
package com.tangem.tap.domain.topup
import com.tangem.Message
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
class TopUpManager {
suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
walletManager.update()
val amountToSend = Amount(walletManager.wallet.blockchain)
val destinationAddress = token.contractAddress
val feeResult = walletManager.getFee(amountToSend,
destinationAddress) as? Result.Success ?: return
val fee = feeResult.data[0]
if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
return
}
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
val signer = TangemSigner(
tangemSdk = tangemSdk, Message()
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.walletSignedHashes,
remainingSignatures = signResponse.walletRemainingSignatures,
walletPublicKey = walletManager.wallet.publicKey
)
)
}
walletManager.send(transaction, signer)
}
}

View file

@ -2,12 +2,14 @@ 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.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.tangemSdkManager
@ -28,7 +30,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
currentCardPublicKey = response.data.walletPublicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = scanNoteResponse.card
)
}
return SimpleResult.failure(response.error)
}
}
}
@ -48,7 +59,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
secondCardPublicKey = response.data.walletPublicKey.toHexString()
return SimpleResult.Success
}
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = scanNoteResponse.card
)
}
return SimpleResult.failure(response.error)
}
}
}
@ -60,7 +80,16 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
)
return when (response) {
is CompletionResult.Success -> Result.Success(response.data)
is CompletionResult.Failure -> Result.failure(response.error)
is CompletionResult.Failure -> {
(response.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData,
card = scanNoteResponse.card
)
}
Result.failure(response.error)
}
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.*
@ -17,10 +18,7 @@ import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
@ -149,6 +147,10 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url)
)
sessions.remove(session)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
@ -247,6 +249,10 @@ class WalletConnectManager {
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
sessionData))
}
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url)
)
}
}
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
@ -256,6 +262,11 @@ class WalletConnectManager {
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.SendTransaction
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
@ -267,6 +278,11 @@ class WalletConnectManager {
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.SignTransaction
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
@ -278,6 +294,11 @@ class WalletConnectManager {
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
FirebaseAnalyticsHandler.WcAction.PersonalSign
)
)
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandlePersonalSignRequest(
message,

View file

@ -1,6 +1,7 @@
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
@ -14,6 +15,7 @@ import com.tangem.commands.SignCommand
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@ -128,6 +130,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error)
null
}
@ -152,6 +160,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + result.data
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)
null
}
@ -208,6 +222,12 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
)
}
Timber.e(result.error.customMessage)
null
}

View file

@ -1,7 +1,9 @@
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.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -90,6 +92,15 @@ class DetailsMiddleware {
is CompletionResult.Success -> {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.PurgeWallet,
card = store.state.detailsState.card
)
}
}
}
}
}
@ -148,8 +159,20 @@ class DetailsMiddleware {
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure, null ->
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error = error,
actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions,
parameters = mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to
(selectedOption?.name ?: "")
),
card = store.state.detailsState.card
)
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
}
}
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.walletconnect.WalletConnectManager
@ -157,7 +158,7 @@ class WalletConnectMiddleware {
wcUri = wcUri,
wallet = WalletForSession(
card.cardId, key.toHexString(),
isTestNet = false
isTestNet = card.isTestCard
),
))
}

View file

@ -0,0 +1,56 @@
package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
class WcDialog {
companion object {
fun create(dialogData: DialogData, context: Context): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(dialogData.title)
setMessage(dialogData.message)
setPositiveButton(dialogData.positiveButton.title) { _, _ ->
dialogData.positiveButton.action()
}
setNegativeButton(dialogData.negativeButton.title) { _, _ ->
dialogData.negativeButton.action()
}
setOnDismissListener { dialogData.onDismissAction() }
}.create()
}
}
}
data class DialogData(
val title: String,
val message: String,
val positiveButton: ButtonData,
val negativeButton: ButtonData,
val onDismissAction: () -> Unit,
)
data class ButtonData(
val title: String,
val action: () -> Unit,
)
//class WcDialogMessageBuilder(val messageRes: Int, val data: DialogMessageData? = null) {
// fun build(context: Context): String {
// when (data) {
// null -> context.getString(messageRes)
// }
// }
//}
interface DialogMessageData
data class WcTransactionDialogMessageData(
val cardId: String,
val dAppName: String,
val dAppUrl: String,
val amount: String,
val gasAmount: String,
val totalAmount: String,
val balance: String,
val isEnoughFundsToSend: Boolean,
)

View file

@ -1,6 +1,7 @@
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.*
@ -127,7 +128,11 @@ private fun sendTransaction(
when (result) {
is SimpleResult.Success -> {
tangemSdk.config.linkedTerminal = isLinkedTerminal
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card)
FirebaseAnalyticsHandler.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain
)
dispatch(SendAction.SendSuccess)
dispatch(NavigationAction.PopBackTo())
scope.launch(Dispatchers.IO) {
@ -178,6 +183,16 @@ private fun sendTransaction(
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
card = card,
)
}
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
import com.tangem.tap.store
import org.rekotlin.Middleware
@ -13,9 +14,13 @@ class TokensMiddleware {
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> {
val cardFirmware = state()?.globalState?.scanNoteResponse?.card?.firmwareVersion
val tokens = currenciesRepository.getPopularTokens()
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
val card = state()?.globalState?.scanNoteResponse?.card
val isTestcard = card?.isTestCard ?: false
val tokens = currenciesRepository.getPopularTokens(isTestcard)
val blockchains = currenciesRepository.getBlockchains(
cardFirmware = card?.firmwareVersion,
isTestNet = isTestcard
)
val currencies = CurrencyListItem.createListOfCurrencies(
blockchains, tokens
)

View file

@ -6,7 +6,6 @@ import com.tangem.blockchain.common.address.AddressType
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.CardCurrencies
@ -19,6 +18,8 @@ sealed class WalletAction : Action {
object ResetState : WalletAction()
data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction()
object LoadData : WalletAction() {
data class Failure(val error: TapError) : WalletAction()
}

View file

@ -32,7 +32,8 @@ data class WalletState(
val cardCurrency: CryptoCurrencyName? = null,
val selectedWallet: Currency? = null,
val primaryBlockchain: Blockchain? = null,
val primaryToken: Token? = null
val primaryToken: Token? = null,
val isTestnet: Boolean = false,
) : StateType {
val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
@ -49,7 +50,9 @@ data class WalletState(
fun getWalletManager(token: Token?): WalletManager? {
if (token == null) return null
val ethereumWalletManager = walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }
val ethereumWalletManager = walletManagers
.find { it.wallet.blockchain == Blockchain.Ethereum ||
it.wallet.blockchain == Blockchain.EthereumTestnet }
return if (ethereumWalletManager?.presetTokens?.contains(token) == true) {
ethereumWalletManager
} else {

View file

@ -8,6 +8,7 @@ import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.features.wallet.redux.Currency
@ -22,7 +23,7 @@ import kotlinx.coroutines.withContext
class MultiWalletMiddleware {
fun handle(
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
) {
when (action) {
is WalletAction.MultiWallet.AddWalletManagers -> {
@ -64,7 +65,10 @@ class MultiWalletMiddleware {
val cardId = globalState?.scanNoteResponse?.card?.cardId
when (val currency = action.walletData.currency) {
is Currency.Blockchain -> {
cardId?.let { currenciesRepository.removeBlockchain(it, currency.blockchain) }
cardId?.let {
currenciesRepository.removeBlockchain(it,
currency.blockchain)
}
}
is Currency.Token -> {
walletState?.getWalletManager(currency.token)
@ -76,8 +80,9 @@ class MultiWalletMiddleware {
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
val cardFirmware = globalState?.scanNoteResponse?.card?.firmwareVersion
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
.filterNot { walletState?.blockchains?.contains(it) == true }
val walletManagers = action.factory.makeWalletManagersForApp(action.card, blockchains)
.filterNot { walletState?.blockchains?.contains(it) == true }
val walletManagers =
action.factory.makeWalletManagersForApp(action.card, blockchains)
scope.launch {
walletManagers.map { walletManager ->
@ -107,10 +112,10 @@ class MultiWalletMiddleware {
is WalletAction.MultiWallet.FindTokensInUse -> {
val card = globalState?.scanNoteResponse?.card ?: return
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = Blockchain.Ethereum
)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = Blockchain.Ethereum
)
val tokenFinder = walletManager as TokenFinder
scope.launch {
val result = tokenFinder.findTokens()
@ -184,7 +189,7 @@ class MultiWalletMiddleware {
val walletManager = walletState?.getWalletManager(token)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = Blockchain.Ethereum
blockchain = if (card.isTestCard) Blockchain.EthereumTestnet else Blockchain.Ethereum
)?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))

View file

@ -2,9 +2,15 @@ package com.tangem.tap.features.wallet.redux.middlewares
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import com.tangem.tap.domain.TopUpHelper
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.tap.domain.topup.TopUpHelper
import com.tangem.tap.domain.topup.TopUpManager
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
class TopUpMiddleware {
fun handle(action: WalletAction.TopUpAction) {
@ -16,16 +22,31 @@ class TopUpMiddleware {
if (addresses.list.isEmpty()) return
val defaultAddress = addresses.list[0].address
val url = TopUpHelper.getUrl(
val currency = selectedWalletData.currency
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
val walletManager = store.state.walletState.getWalletManager(currency.token)
if (walletManager !is EthereumWalletManager) return
scope.launch {
TopUpManager().topUpTestErc20Tokens(
walletManager = walletManager, token = currency.token
)
}
} else {
val url = TopUpHelper.getUrl(
currency?.blockchain,
selectedWalletData.currencyData.currencySymbol!!,
defaultAddress,
config.moonPayApiKey,
config.moonPayApiSecretKey
)
val customTabsIntent = CustomTabsIntent.Builder()
)
val customTabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(action.toolbarColor)
.build()
customTabsIntent.launchUrl(action.context, Uri.parse(url));
customTabsIntent.launchUrl(action.context, Uri.parse(url))
}
}
}
}

View file

@ -3,6 +3,7 @@ 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.extensions.isZero
@ -43,7 +44,6 @@ class WalletMiddleware {
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
is WalletAction.MultiWallet ->
multiWalletMiddleware.handle(action, walletState, globalState)
is WalletAction.LoadWallet -> {
scope.launch {
if (action.blockchain == null) {
@ -111,6 +111,15 @@ class WalletMiddleware {
globalState.tapWalletManager.onCardScanned(scanNoteResponse)
}
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
card = store.state.detailsState.card
)
}
}
}
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.extensions.getSingleWallet
@ -84,6 +85,11 @@ class WarningsMiddleware {
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
globalState?.scanNoteResponse?.card?.let { card ->
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
if (card.isTestCard) {
addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
return@let
}
showWarningLowRemainingSignaturesIfNeeded(card)
if (card.getType() != CardType.Release) {
addWarningMessage(WarningMessagesManager.devCardWarning())

View file

@ -39,6 +39,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
is WalletAction.ResetState -> newState = WalletState()
is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet)
is WalletAction.EmptyWallet -> {
val creatingWalletAllowed = !(newState.twinCardsState != null &&
newState.twinCardsState?.isCreatingTwinCardsAllowed != true)

View file

@ -99,6 +99,14 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
val selectedWallet = state.getSelectedWalletData() ?: return
tv_currency_title.text = selectedWallet.currencyData.currency
val currency = selectedWallet.currency
if (currency is Currency.Token) {
tv_currency_subtitle.text = currency.blockchain.tokenDisplayName()
tv_currency_subtitle.show()
} else {
tv_currency_subtitle.hide()
}
showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
setupAddressCard(selectedWallet)

View file

@ -98,7 +98,7 @@ class WalletAdapter
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
textView = view.tv_token_letter,
token = token, blockchain = blockchain
token = token, blockchain = blockchain,
)
when (wallet.currencyData.status) {

View file

@ -73,7 +73,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
}
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
WarningMessage.Type.Permanent -> {
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
view.group_controls_temporary.hide()
view.group_controls_rating.hide()
}

View file

@ -52,7 +52,6 @@
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="@color/darkGray6"
android:textSize="32sp"
android:textStyle="bold"
@ -60,6 +59,26 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="Bitcoin" />
<TextView
android:id="@+id/tv_currency_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textColor="@color/darkGray1"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_currency_title"
tools:text="Ethereum token" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_currency_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="tv_currency_subtitle,tv_currency_title" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pending_transaction"
android:layout_width="match_parent"
@ -69,13 +88,15 @@
android:overScrollMode="never"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_currency_title" />
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/barrier_currency_title" />
<include
android:id="@+id/l_wallet_details"
layout="@layout/layout_wallet_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />

View file

@ -33,7 +33,7 @@
<TextView
android:id="@+id/tv_currency_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
@ -42,7 +42,7 @@
android:textColor="@color/darkGray6"
android:textSize="17sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/btn_add_token"
app:layout_constraintEnd_toStartOf="@id/barrier_chip_button"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/iv_currency"
app:layout_constraintTop_toTopOf="@id/iv_currency"
@ -93,4 +93,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_chip_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="btn_add_token, btn_token_added" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -43,6 +43,8 @@
<color name="blue3">#CBE4FF</color>
<color name="blue_pale">#E0E6FA</color>
<color name="tint">#55000000</color>
<color name="warning_info">#1C1C1E</color>
<color name="warning_warning">#FFB71B</color>
<color name="warning_critical">#CA0F03</color>

View file

@ -157,4 +157,6 @@ this wallet.
<string name="wallet_connect_could_not_complete" translatable="false">The operation couldn\'t be completed</string>
<string name="wallet_connect_same_wcuri" translatable="false">The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters.</string>
<string name="wallet_connect_error_timeout" translatable="false">Failed to establish WalletConnect session: timeout error. Please, try again later.</string>
<string name="warning_testnet_card_message" translatable="false">This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes.</string>
</resources>