Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-19 09:56:40 +04:00
commit 886f0d66bc
54 changed files with 378 additions and 532 deletions

View file

@ -31,13 +31,13 @@ sealed class TapError(
object InsufficientBalance : TapError(R.string.send_error_insufficient_balance)
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
data class AmountLowerExistentialDeposit(override val args: List<Any>) : TapError(R.string.send_error_minimum_balance_format)
object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee)
object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total)
object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount)
object InvalidFeeValue : TapError(R.string.send_error_invalid_fee_value)
data class DustAmount(override val args: List<Any>) : TapError(R.string.send_error_dust_amount_format)
object DustChange : TapError(R.string.send_error_dust_change)
data class CreateAccountUnderfunded(override val args: List<Any>) : TapError(R.string.send_error_no_target_account)
data class UnsupportedState(
val stateError: String,

View file

@ -23,6 +23,7 @@ import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
@ -88,14 +89,16 @@ class TapWalletManager {
withMainContext {
store.dispatch(WalletAction.ResetState(data.card.cardId))
store.dispatch(WalletConnectAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
store.dispatch(WalletConnectAction.RestoreSessions(data))
store.dispatch(
WalletAction.MultiWallet.ShowWalletBackupWarning(
show = data.card.settings.isBackupAllowed
&& data.card.backupStatus == Card.BackupStatus.NoBackup
)
&& data.card.backupStatus == Card.BackupStatus.NoBackup,
),
)
loadData(data)
}

View file

@ -1,12 +1,8 @@
package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.AssetReader
import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
@ -14,8 +10,8 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi,
private val assetReader: AssetReader,
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
@ -23,8 +19,8 @@ class FeaturesLocalLoader(
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
val jsonFeatures = context.readAssetAsString(Loader.featuresName)
val jsonConfigValues = context.readAssetAsString(Loader.configValuesName)
val jsonFeatures = assetReader.readAssetAsString(Loader.featuresName)
val jsonConfigValues = assetReader.readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@ -33,31 +29,4 @@ class FeaturesLocalLoader(
}
onComplete(config)
}
}
class FeaturesRemoteLoader(
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig), null))
} else {
onComplete(emptyConfig)
}
}.addOnFailureListener {
FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}
}

View file

@ -24,26 +24,19 @@ data class Config(
val zendesk: ZendeskConfig? = null,
)
class ConfigManager(
private val localLoader: Loader<ConfigModel>,
private val remoteLoader: Loader<ConfigModel>
) {
class ConfigManager {
var config: Config = Config()
private set
private var defaultConfig = Config()
fun load(onComplete: ((config: Config) -> Unit)? = null) {
localLoader.load { configModel ->
fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null) {
configLoader.load { configModel ->
setupFeature(configModel.features)
setupKey(configModel.configValues)
onComplete?.invoke(config)
}
// Uncomment to enable remote config
// remoteLoader.load { config ->
// setupFeature(config.features)
// }
}
fun turnOff(name: String) {

View file

@ -1,52 +0,0 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
*/
class RemoteWarningLoader(
private val moshi: Moshi,
) : Loader<List<WarningMessage>> {
override fun load(onComplete: (List<WarningMessage>) -> Unit) {
val emptyConfig = listOf<WarningMessage>()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (!it.isSuccessful) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val config = remoteConfig.getValue(Loader.warnings)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val adapterType = Types.newParameterizedType(List::class.java, WarningMessage::class.java)
val warningsAdapter: JsonAdapter<List<WarningMessage>> = moshi.adapter(adapterType)
try {
val warnings = warningsAdapter.fromJson(jsonConfig) ?: listOf()
onComplete(warnings)
} catch (ex: Exception) {
handleError(ex)
onComplete(emptyConfig)
}
}.addOnFailureListener {
handleError(it)
onComplete(emptyConfig)
}
}
private fun handleError(ex: Exception) {
FirebaseAnalyticsHandler.logException("remote_config_error.warnings", ex)
}
}

View file

@ -1,35 +1,17 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
class WarningMessagesManager(
private val warningLoader: RemoteWarningLoader,
) {
class WarningMessagesManager {
private val warningsList: MutableList<WarningMessage> = mutableListOf()
fun load(onComplete: VoidCallback? = null) {
// exclude annoying remote debug warnings
if (BuildConfig.DEBUG) {
onComplete?.invoke()
return
}
warningLoader.load { remoteList ->
warningsList.clear()
warningsList.addAll(remoteList)
sortByPriority()
onComplete?.invoke()
}
}
fun addWarning(warning: WarningMessage) {
if (findWarning(warning) == null) {
warningsList.add(warning)
@ -37,7 +19,10 @@ class WarningMessagesManager(
}
}
fun getWarnings(location: WarningMessage.Location, forBlockchains: List<Blockchain> = emptyList()): List<WarningMessage> {
fun getWarnings(
location: WarningMessage.Location,
forBlockchains: List<Blockchain> = emptyList(),
): List<WarningMessage> {
return warningsList
.filter { !it.isHidden && it.location.contains(location) }
.filter {
@ -94,7 +79,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_developer_card,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
@ -106,7 +91,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
@ -131,7 +116,7 @@ class WarningMessagesManager(
null,
R.string.warning_rate_app_title,
R.string.warning_rate_app_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
@ -147,7 +132,7 @@ class WarningMessagesManager(
null,
R.string.warning_failed_to_verify_card_title,
R.string.warning_failed_to_verify_card_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
@ -160,7 +145,7 @@ class WarningMessagesManager(
titleResId = R.string.alert_title,
messageResId = R.string.warning_low_signatures_format,
origin = WarningMessage.Origin.Local,
messageFormatArg = remainingSignatures.toString()
messageFormatArg = remainingSignatures.toString(),
)
fun testCardWarning(): WarningMessage = WarningMessage(
@ -172,7 +157,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.warning_testnet_card_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
fun demoCardWarning(): WarningMessage = WarningMessage(
@ -184,7 +169,7 @@ class WarningMessagesManager(
null,
R.string.alert_title,
R.string.alert_demo_message,
WarningMessage.Origin.Local
WarningMessage.Origin.Local,
)
const val REMAINING_SIGNATURES_WARNING = 10

View file

@ -13,6 +13,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.domain.common.ScanResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.tangemSdkManager
@ -119,10 +120,6 @@ class TwinCardsManager(
}
}
interface AssetReader {
fun readAssetAsString(name: String): String
}
private class Issuer(
val id: String,
val privateKey: String,

View file

@ -1,7 +1,9 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
@ -69,7 +71,7 @@ class WalletConnectManager {
remotePeerId = null,
session = session,
client = client,
wallet = WalletForSession(cardId = "")
wallet = WalletForSession(),
)
setupConnectionTimeoutCheck(session)
}
@ -110,8 +112,12 @@ class WalletConnectManager {
}
}
fun restoreSessions() {
fun restoreSessions(scanResponse: ScanResponse) {
val walletPublicKey = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }?.publicKey
?: return
val sessions = walletConnectRepository.loadSavedSessions()
// filter sessions for this particular card
.filter { it.wallet.walletPublicKey.contentEquals(walletPublicKey) }
this.sessions = sessions
.map { session ->
WalletConnectActiveData(
@ -126,8 +132,8 @@ class WalletConnectManager {
setListeners(it.client)
it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId)
}
}
.map { it.session to it }.toMap().toMutableMap()
}.associateBy { it.session }.toMutableMap()
store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions))
}

View file

@ -12,6 +12,9 @@ class WalletConnectNetworkUtils {
peer: WCPeerMeta,
): Blockchain? {
return when {
peer.url.contains("pancakeswap.finance") -> {
Blockchain.BSC
}
chainId != null -> {
Blockchain.fromChainId(chainId)
}

View file

@ -92,7 +92,6 @@ class WalletConnectSdkHelper {
)
)
val dialogData = TransactionRequestDialogData(
cardId = session.wallet.cardId,
dAppName = session.peerMeta.name,
dAppUrl = session.peerMeta.url,
amount = value.toFormattedString(decimals),
@ -235,7 +234,6 @@ class WalletConnectSdkHelper {
val dialogData = PersonalSignDialogData(
cardId = session.wallet.cardId,
dAppName = session.peerMeta.name,
message = messageString,
session = session.session,
@ -272,11 +270,11 @@ class WalletConnectSdkHelper {
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey
val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath)
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
return when (val result = tangemSdkManager.runTaskAsync(command)) {
is CompletionResult.Success -> {
val hash = result.data.signature
return EthereumUtils.prepareSignedMessageData(
hash, hashToSign, CryptoUtils.decompressPublicKey(key!!)
hash, hashToSign, CryptoUtils.decompressPublicKey(key!!),
)
}
is CompletionResult.Failure -> {

View file

@ -1,13 +1,10 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.card.EllipticCurve
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.getPrimaryCurve
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
@ -17,51 +14,25 @@ import com.tangem.tap.features.wallet.redux.WalletState
class WcWalletManagerFactory(
private val factory: WalletManagerFactory,
private val currenciesRepository: CurrenciesRepository,
) {
suspend fun getWalletManager(
wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState
) {
fun getWalletManager(
wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState,
): WalletManager? {
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
Blockchain.EthereumTestnet
} else {
blockchain
}
val blockchainNetwork = BlockchainNetwork(
blockchain = blockchainToMake,
derivationPath = wallet.derivationPath?.rawPath,
tokens = emptyList()
tokens = emptyList(),
)
return if (walletState.cardId == wallet.cardId) {
walletState.getWalletManager(blockchainNetwork)
} else {
val blockchainNetworkWithTokens = currenciesRepository
.loadSavedCurrencies(
cardId = wallet.cardId,
isHdWalletSupported = wallet.derivationPath != null
).firstOrNull { it == blockchainNetwork }
if (blockchainNetworkWithTokens != null) {
factory.makeWalletManager(
blockchain = blockchainToMake,
publicKey = Wallet.PublicKey(
wallet.walletPublicKey!!,
wallet.derivedPublicKey,
wallet.derivationPath
),
tokens = blockchainNetworkWithTokens.tokens,
curve = blockchainToMake.getPrimaryCurve() ?: EllipticCurve.Secp256k1
)
} else {
null
}
}
return walletState.getWalletManager(blockchainNetwork)
}
suspend fun getWalletManager(
scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState
scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState,
): WalletManager? {
val card = scanResponse.card
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
@ -69,14 +40,13 @@ class WcWalletManagerFactory(
} else {
blockchain
}
val blockchainNetwork = BlockchainNetwork(
blockchain = blockchainToMake,
card = card
card = card,
)
return if (walletState.cardId == card.cardId) {
walletState.getWalletManager(blockchainNetwork)
walletState.getWalletManager(blockchainNetwork)
} else {
if (currenciesRepository
.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
@ -84,7 +54,7 @@ class WcWalletManagerFactory(
) {
factory.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchainNetwork = blockchainNetwork
blockchainNetwork = blockchainNetwork,
)
} else {
null