diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b460cf581a..4422e037f9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,7 +7,6 @@
android:name="android.hardware.nfc"
android:required="true" />
-
@@ -131,18 +130,6 @@
-
-
-
-
-
-
-
-
R.drawable.ic_dogecoin_round
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round
Blockchain.Gnosis -> R.drawable.ic_gnosis_round
+ Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round
+ Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round
+ Blockchain.Kusama -> R.drawable.ic_kusama_round
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism
Blockchain.Dash -> R.drawable.ic_dash
else -> R.drawable.ic_tangem_logo
@@ -58,7 +61,10 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
+ Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_no_color
+ Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_no_color
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_no_color
+ Blockchain.Kusama -> R.drawable.ic_kusama_no_color
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color
Blockchain.Dash -> R.drawable.ic_dash_no_color
else -> R.drawable.ic_tangem_logo
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
index 70912f24a6..97c8a9e41f 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
@@ -7,16 +7,19 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.address.Address
+import com.tangem.common.card.CardWallet
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.extensions.stripZeroPlainString
class AdditionalFeedbackInfo {
class EmailWalletInfo(
var blockchain: Blockchain = Blockchain.Unknown,
- var address: String = "",
- var explorerLink: String = "",
- var host: String = "",
var derivationPath: String = "",
+ var outputsCount: String? = null,
+ var host: String = "",
+ var addresses: String = "",
+ var explorerLink: String = "",
)
var appVersion: String = ""
@@ -48,23 +51,14 @@ class AdditionalFeedbackInfo {
cardBlockchain = data.walletData?.blockchain ?: ""
cardFirmwareVersion = data.card.firmwareVersion.stringValue
cardIssuer = data.card.issuer.name
- signedHashesCount = data.card.wallets
- .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
+ signedHashesCount = formatSignedHashes(data.card.wallets)
}
fun setWalletsInfo(walletManagers: List) {
walletsInfo.clear()
tokens.clear()
walletManagers.forEach { manager ->
- walletsInfo.add(
- EmailWalletInfo(
- blockchain = manager.wallet.blockchain,
- address = getAddress(manager.wallet),
- explorerLink = getExploreUri(manager.wallet),
- host = manager.currentHost,
- derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
- )
- )
+ walletsInfo.add(createEmailWalletInfo(manager))
if (manager.cardTokens.isNotEmpty()) {
tokens[manager.wallet.blockchain] = manager.cardTokens
}
@@ -72,45 +66,55 @@ class AdditionalFeedbackInfo {
}
fun updateOnSendError(
- wallet: Wallet,
- host: String,
+ walletManager: WalletManager,
amountToSend: Amount,
feeAmount: Amount,
destinationAddress: String,
) {
- onSendErrorWalletInfo = EmailWalletInfo(
- blockchain = wallet.blockchain,
- address = getAddress(wallet),
- explorerLink = getExploreUri(wallet),
- host = host,
- derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
- )
-
+ onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
this.destinationAddress = destinationAddress
amount = amountToSend.value?.stripZeroPlainString() ?: "0"
fee = feeAmount.value?.stripZeroPlainString() ?: "0"
token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
}
- private fun getAddress(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.address
- } else {
- val addresses = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${it.value}"
- }
- "Multiple address: $addresses"
+ private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo {
+ return EmailWalletInfo(
+ blockchain = walletManager.wallet.blockchain,
+ derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "",
+ outputsCount = walletManager.outputsCount?.toString(),
+ host = walletManager.currentHost,
+ addresses = formatAddresses(walletManager.wallet),
+ explorerLink = formatExploreUrls(walletManager.wallet),
+ )
+ }
+
+ private fun formatSignedHashes(wallets: List): String {
+ return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
+ }
+
+ private fun formatAddresses(wallet: Wallet): String {
+ return wallet.formatAddressWith("Multiple address:") {
+ "${it.name} - ${it.value}"
}
}
- private fun getExploreUri(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.getExploreUrl(wallet.address)
- } else {
- val links = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
- }
- "Multiple explorers links: $links"
+ private fun formatExploreUrls(wallet: Wallet): String {
+ return wallet.formatAddressWith("Multiple explorers links:") {
+ "${it.name} - ${wallet.getExploreUrl(it.value)}"
}
}
+
+ private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
+ return if (addresses.size == 1) {
+ getExploreUrl(address)
+ } else {
+ addresses.map { mapAddress(it) }.toMutableList()
+ .apply { add(0, with) }
+ .joinToString("\n")
+ }
+ }
+
+ private val Address.name: String
+ get() = type.javaClass.simpleName
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
index a3a9ee5575..46cf302fee 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.common.feedback
import com.tangem.tap.common.extensions.breakLine
class FeedbackDataBuilder(
- private val infoHolder: AdditionalFeedbackInfo
+ private val infoHolder: AdditionalFeedbackInfo,
) {
val builder = StringBuilder()
@@ -21,7 +21,7 @@ class FeedbackDataBuilder(
builder.appendKeyValue("Card ID", infoHolder.cardId)
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
- builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+ builder.appendKeyValue("", infoHolder.signedHashesCount)
return this
}
@@ -29,20 +29,22 @@ class FeedbackDataBuilder(
infoHolder.walletsInfo.forEach {
builder.appendDelimiter()
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
- builder.appendKeyValue("Host", it.host)
- builder.appendKeyValue("Wallet address", it.address)
builder.appendKeyValue("Derivation path", it.derivationPath)
- builder.appendKeyValue("Explorer link", it.explorerLink)
+ builder.appendKeyValue("Outputs count", it.outputsCount)
infoHolder.tokens[it.blockchain]?.let { tokens ->
builder.append("Tokens:")
breakLine()
tokens.forEach { token ->
- builder.appendKeyValue("Name", token.name)
builder.appendKeyValue("ID", token.id ?: "[custom token]")
+ builder.appendKeyValue("Name", token.name)
builder.appendKeyValue("Contract address", token.contractAddress)
}
}
+
+ builder.appendKeyValue("Host", it.host)
+ builder.appendKeyValue("Wallet address", it.addresses)
+ builder.appendKeyValue("Explorer link", it.explorerLink)
}
return this
}
@@ -55,7 +57,7 @@ class FeedbackDataBuilder(
builder.appendKeyValue("Token", infoHolder.token)
builder.appendKeyValue("Error", error)
builder.appendDelimiter()
- builder.appendKeyValue("Source address", walletInfo.address)
+ builder.appendKeyValue("Source address", walletInfo.addresses)
builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
builder.appendKeyValue("Amount", infoHolder.amount)
builder.appendKeyValue("Fee", infoHolder.fee)
@@ -72,8 +74,10 @@ class FeedbackDataBuilder(
fun build(): String = builder.toString()
}
-private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
- return if (value.isNotBlank()) this.append("$key: $value\n") else this
+private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when {
+ value.isNullOrBlank() -> this
+ key.isBlank() -> this.append("$value\n")
+ else -> this.append("$key: $value\n")
}
private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
\ No newline at end of file
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 e3b1dfb9af..8173f76aae 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
@@ -12,7 +12,6 @@ import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
@@ -25,6 +24,7 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
+import com.tangem.tap.userTokensRepository
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
@@ -141,7 +141,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
scope.launch {
val result = tangemSdkManager.scanProduct(
store.state.globalState.analyticsHandlers,
- currenciesRepository,
+ userTokensRepository,
action.additionalBlockchainsToDerive,
action.messageResId,
)
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index 4833079ad0..34e58f0ba7 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -32,7 +32,7 @@ import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
-import com.tangem.tap.domain.tokens.CurrenciesRepository
+import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
@@ -45,7 +45,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
suspend fun scanProduct(
analyticsHandler: AnalyticsHandler?,
- currenciesRepository: CurrenciesRepository,
+ userTokensRepository: UserTokensRepository,
additionalBlockchainsToDerive: Collection? = null,
messageRes: Int? = null,
): CompletionResult {
@@ -53,8 +53,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
- runnable = ScanProductTask(null, currenciesRepository, additionalBlockchainsToDerive),
- cardId = null, initialMessage = message
+ runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive),
+ cardId = null, initialMessage = message,
).also { sendScanResultsToAnalytics(analyticsHandler, it) }
}
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 8219a52319..6e73a11fba 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.common.core.TangemError
+import com.tangem.network.api.tangemTech.TangemTechError
import com.tangem.wallet.R
interface TapErrors
@@ -41,7 +42,7 @@ sealed class TapError(
data class UnsupportedState(
val stateError: String,
- val customMessage: String = "Unsupported state:"
+ val customMessage: String = "Unsupported state:",
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class WalletManager {
@@ -52,9 +53,14 @@ sealed class TapError(
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
}
+ sealed class WalletConnect {
+ object UnsupportedDapp : TapError(R.string.wallet_connect_error_unsupported_dapp)
+ object UnsupportedLink : TapError(R.string.wallet_connect_error_failed_to_connect)
+ }
+
data class ValidateTransactionErrors(
override val errorList: List,
- override val builder: (List) -> String
+ override val builder: (List) -> String,
) : TapError(-1), MultiMessageError
}
@@ -67,7 +73,6 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE
object ScanPrimaryCard : TapSdkError(R.string.saltpay_backup_warning)
}
-
fun TapErrors.assembleErrors(): MutableList?>> {
val idList = mutableListOf?>>()
when (this) {
@@ -75,4 +80,13 @@ fun TapErrors.assembleErrors(): MutableList?>> {
is TapError -> idList.add(Pair(this.messageResource, this.args))
}
return idList
-}
\ No newline at end of file
+}
+
+fun TangemTechError.toTapError(): TapError {
+ return when (this.code) {
+ 404 -> NoDataError(this.description)
+ else -> TapError.CustomError(customMessage = this.description)
+ }
+}
+
+class NoDataError(message: String) : TapError.CustomError(customMessage = message)
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 507e6023d4..eae80ac060 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -16,7 +16,6 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
@@ -24,17 +23,17 @@ 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.models.toBlockchainNetworks
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
+import com.tangem.tap.userTokensRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
- by lazy { WalletManagerFactory(blockchainSdkConfig) }
-
+ by lazy { WalletManagerFactory(blockchainSdkConfig) }
val rates: RatesRepository = RatesRepository()
private val blockchainSdkConfig by lazy {
@@ -65,16 +64,16 @@ class TapWalletManager {
WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
blockchainNetwork,
- (result.error as TapError.WalletManager.NoAccountError).customMessage
- )
+ (result.error as TapError.WalletManager.NoAccountError).customMessage,
+ ),
)
}
else -> {
dispatchOnMain(
WalletAction.LoadWallet.Failure(
walletManager.wallet,
- result.error.localizedMessage
- )
+ result.error.localizedMessage,
+ ),
)
}
}
@@ -138,24 +137,23 @@ class TapWalletManager {
dispatchOnMain(WalletAction.LoadFiatRate())
}
- private suspend fun loadMultiWalletData(
- scanResponse: ScanResponse
- ) {
- val savedCurrencies = currenciesRepository.loadSavedCurrencies(
- scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed
- )
- if (savedCurrencies.isEmpty()) return
+ private suspend fun loadMultiWalletData(scanResponse: ScanResponse) {
+ loadUserCurrencies(scanResponse, walletManagerFactory)
+ }
- val walletManagers =
- walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
- dispatchOnMain(
- WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
- )
- savedCurrencies.map {
+ private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) {
+ blockchainNetworks.map {
if (it.tokens.isNotEmpty()) {
- dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
+ WalletAction.MultiWallet.AddTokens(it.tokens, it, false)
}
}
+ val missingDerivations = blockchainNetworks
+ .filter {
+ it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath)
+ }
+ if (missingDerivations.isNotEmpty()) {
+ store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations))
+ }
}
private suspend fun loadSingleWalletData(data: ScanResponse) {
@@ -163,7 +161,6 @@ class TapWalletManager {
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
- val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
val primaryToken = data.getPrimaryToken()
dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
@@ -173,14 +170,43 @@ class TapWalletManager {
}
dispatchOnMain(
WalletAction.MultiWallet.AddBlockchains(
- listOf(blockchainNetwork),
- listOf(primaryWalletManager),
+ blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
+ walletManagers = listOf(primaryWalletManager),
+ save = false,
),
)
}
}
+ private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) {
+ val userTokens = userTokensRepository.getUserTokens(scanResponse.card)
+ withMainContext {
+ val blockchainNetworks = userTokens.toBlockchainNetworks()
+ val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, userTokens)
+ store.dispatch(
+ WalletAction.MultiWallet.AddBlockchains(
+ blockchains = blockchainNetworks,
+ walletManagers = walletManagers,
+ save = false,
+ ),
+ )
+
+ blockchainNetworks.filter { it.tokens.isNotEmpty() }
+ .map {
+ store.dispatch(
+ WalletAction.MultiWallet.AddTokens(
+ tokens = it.tokens,
+ blockchain = it,
+ save = false,
+ ),
+ )
+ }
+ checkIfDerivationsAreMissing(blockchainNetworks, scanResponse)
+ }
+ }
+
suspend fun reloadData(data: ScanResponse) {
+ loadUserCurrencies(data, walletManagerFactory)
withContext(Dispatchers.Main) {
getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
store.dispatch(it)
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index 654eb2185c..175fb62544 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
@@ -103,9 +103,11 @@ fun WalletManagerFactory.makeWalletManagerForApp(
}
fun WalletManagerFactory.makeWalletManagersForApp(
- scanResponse: ScanResponse, blockchains: List,
+ scanResponse: ScanResponse, blockchains: List,
): List {
- return blockchains.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
+ return blockchains
+ .filter { it.isBlockchain() }
+ .mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
}
fun WalletManagerFactory.makePrimaryWalletManager(
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
index 1963d69143..cdef91880f 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
@@ -21,7 +21,6 @@ import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.TwinsHelper
import com.tangem.domain.common.isTangemTwins
-import com.tangem.domain.common.productType
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.ScanTask
@@ -32,19 +31,17 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.extensions.getPrimaryCurve
import com.tangem.tap.domain.extensions.getSingleWallet
+import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.extensions.hasNoWallets
import com.tangem.tap.domain.extensions.isHdWalletAllowedByApp
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
-import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.preferencesStorage
-import com.tangem.tap.scope
-import kotlinx.coroutines.launch
class ScanProductTask(
val card: Card? = null,
- private val currenciesRepository: CurrenciesRepository?,
- private val additionalBlockchainsToDerive: Collection? = null
+ private val userTokensRepository: UserTokensRepository?,
+ private val additionalBlockchainsToDerive: Collection? = null,
) : CardSessionRunnable {
override fun run(
@@ -64,7 +61,7 @@ class ScanProductTask(
val commandProcessor = when {
card.isTangemTwins() -> ScanTwinProcessor()
- else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive)
+ else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive)
}
commandProcessor.proceed(card, session) { processorResult ->
when (processorResult) {
@@ -95,8 +92,8 @@ class ScanProductTask(
}
private class ScanWalletProcessor(
- private val currenciesRepository: CurrenciesRepository?,
- private val additionalBlockchainsToDerive: Collection? = null
+ private val userTokensRepository: UserTokensRepository?,
+ private val additionalBlockchainsToDerive: Collection? = null,
) : ProductCommandProcessor {
var primaryCard: PrimaryCard? = null
@@ -169,47 +166,41 @@ private class ScanWalletProcessor(
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
- scope.launch {
- val derivations = collectDerivations(card)
- if (derivations.isEmpty() || !card.isHdWalletAllowedByApp) {
- callback(
- CompletionResult.Success(
- ScanResponse(
- card = card,
- productType = card.productType,
- walletData = session.environment.walletData,
- primaryCard = primaryCard,
- ),
+ val derivations = collectDerivations(card)
+ if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
+ callback(
+ CompletionResult.Success(
+ ScanResponse(
+ card = card,
+ productType = ProductType.Wallet,
+ walletData = session.environment.walletData,
+ primaryCard = primaryCard,
),
- )
- return@launch
- }
+ ),
+ )
+ return
+ }
- DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
- when (result) {
- is CompletionResult.Success -> {
- val response = ScanResponse(
- card = card,
- productType = card.productType,
- walletData = session.environment.walletData,
- derivedKeys = result.data.entries,
- primaryCard = primaryCard,
- )
- callback(CompletionResult.Success(response))
- }
- is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
+ DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
+ when (result) {
+ is CompletionResult.Success -> {
+ val response = ScanResponse(
+ card = card,
+ productType = ProductType.Wallet,
+ walletData = session.environment.walletData,
+ derivedKeys = result.data.entries,
+ primaryCard = primaryCard,
+ )
+ callback(CompletionResult.Success(response))
}
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
- private suspend fun getBlockchainsToDerive(card: Card): List {
- val currenciesRepository = currenciesRepository ?: return emptyList()
-
- val cardCurrencies = currenciesRepository
- .loadSavedCurrencies(card.cardId, card.isHdWalletAllowedByApp).toMutableList()
-
- val blockchainsToDerive = cardCurrencies.ifEmpty {
+ private fun getBlockchainsToDerive(card: Card): List {
+ val userTokensRepository = userTokensRepository ?: return emptyList()
+ val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card).toMutableList().ifEmpty {
mutableListOf(
BlockchainNetwork(Blockchain.Bitcoin, card),
BlockchainNetwork(Blockchain.Ethereum, card),
@@ -221,7 +212,7 @@ private class ScanWalletProcessor(
listOf(
BlockchainNetwork(Blockchain.Ethereum, card),
BlockchainNetwork(Blockchain.EthereumTestnet, card),
- )
+ ),
)
}
if (additionalBlockchainsToDerive != null) {
@@ -241,13 +232,12 @@ private class ScanWalletProcessor(
return blockchainsToDerive.distinct()
}
- private suspend fun collectDerivations(card: Card): Map> {
+ private fun collectDerivations(card: Card): Map> {
val blockchains = getBlockchainsToDerive(card)
val derivations = mutableMapOf>()
blockchains.forEach { blockchain ->
val curve = blockchain.blockchain.getPrimaryCurve()
-
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
if (wallet.chainCode == null) return@forEach
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
index 2dafa32075..d3d5836701 100644
--- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
@@ -1,247 +1,12 @@
package com.tangem.tap.domain.tokens
-import android.app.Application
-import android.content.Context
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.Types
import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.DerivationStyle
-import com.tangem.blockchain.common.Token
import com.tangem.common.card.FirmwareVersion
-import com.tangem.common.services.Result
-import com.tangem.domain.common.extensions.getTokens
-import com.tangem.domain.common.extensions.toNetworkId
-import com.tangem.network.api.tangemTech.TangemTechService
-import com.tangem.network.common.MoshiConverter
-import com.tangem.tap.common.extensions.appendIf
-import com.tangem.tap.common.extensions.readJsonFileToString
-import com.tangem.tap.domain.tokens.models.BlockchainNetwork
-import com.tangem.tap.domain.tokens.models.ObsoleteTokenDao
-import com.tangem.tap.domain.tokens.models.TokenDao
-import com.tangem.tap.features.demo.DemoHelper
-import kotlinx.coroutines.async
-import kotlinx.coroutines.coroutineScope
-import timber.log.Timber
-import java.util.*
-
-class CurrenciesRepository(
- private val context: Application,
- private val tangemNetworkService: TangemTechService
-) {
-
- private val moshi = MoshiConverter.defaultMoshi()
- private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
- Types.newParameterizedType(List::class.java, Blockchain::class.java)
- )
- private val tokensAdapter: JsonAdapter> = moshi.adapter(
- Types.newParameterizedType(List::class.java, TokenDao::class.java)
- )
- private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter(
- Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
- )
- private val currenciesAdapter: JsonAdapter =
- moshi.adapter(CurrenciesFromJson::class.java)
-
- private val blockchainNetworkAdapter: JsonAdapter> =
- moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
-
- fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
- var changed = false
- val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
- if (it == blockchainNetwork) {
- changed = true
- blockchainNetwork
- } else {
- it
- }
- }
- val updatedCurrencies = if (changed) currencies else currencies + blockchainNetwork
- saveCurrencies(cardId, updatedCurrencies.distinct())
- }
-
- fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
- val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
- if (it == blockchainNetwork) {
- it.copy(tokens = it.tokens.filterNot { it == token })
- } else {
- it
- }
- }
- saveCurrencies(cardId, currencies)
- }
-
- fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
- val currencies = loadSavedCurrenciesWithoutMigration(cardId)
- .filterNot { it == blockchainNetwork }
- saveCurrencies(cardId, currencies)
- }
-
- fun removeCurrencies(cardId: String) {
- saveCurrencies(cardId, emptyList())
- }
-
- @Deprecated("Use BlockchainNetwork instead")
- private fun loadSavedTokens(cardId: String): List {
- val json = try {
- context.readFileText(getFileNameForTokens(cardId))
- } catch (exception: Exception) {
- return emptyList()
- }
-
- return try {
- tokensAdapter.fromJson(json) ?: emptyList()
- } catch (exception: Exception) {
- emptyList()
- }
- }
-
- @Deprecated("Use BlockchainNetwork instead")
- private fun loadSavedBlockchains(cardId: String): List {
- return try {
- val json = context.readFileText(getFileNameForBlockchains(cardId))
- blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
- } catch (exception: Exception) {
- emptyList()
- }
- }
-
- suspend fun loadSavedCurrencies(
- cardId: String,
- isHdWalletSupported: Boolean = false
- ): List {
- if (DemoHelper.isDemoCardId(cardId)) {
- return loadDemoCurrencies()
- }
- return try {
- val json = context.readFileText(getFileNameForBlockchains(cardId))
- blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
- } catch (exception: Exception) {
- tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
- }
- }
-
- fun loadSavedCurrenciesWithoutMigration(
- cardId: String,
- ): List {
- if (DemoHelper.isDemoCardId(cardId)) {
- return loadDemoCurrencies()
- }
- return try {
- val json = context.readFileText(getFileNameForBlockchains(cardId))
- blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
- } catch (exception: Exception) {
- emptyList()
- }
- }
-
- private fun loadDemoCurrencies(): List {
- return DemoHelper.config.demoBlockchains.map {
- BlockchainNetwork(
- blockchain = it,
- derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
- tokens = emptyList()
- )
- }
- }
-
- private suspend fun tryToLoadPreviousFormatAndMigrate(
- cardId: String,
- isHdWalletSupported: Boolean = false
- ): List {
- return try {
- loadSavedCurrenciesOldWay(
- cardId,
- isHdWalletSupported
- )
- } catch (exception: Exception) {
- emptyList()
- }
- }
-
- private suspend fun loadSavedCurrenciesOldWay(
- cardId: String, isHdWalletSupported: Boolean = false
- ): List {
- val blockchains = loadSavedBlockchains(cardId)
- val tokens = loadSavedTokens(cardId)
- val ids = getTokensIds(tokens)
- val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
- val blockchainNetworks = blockchains.map { blockchain ->
- BlockchainNetwork(
- blockchain = blockchain,
- derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
- tokens = tokens
- .filter { it.blockchainDao.toBlockchain() == blockchain }
- .map {
- val token = it.toToken()
- token.copy(id = ids[token.contractAddress])
- }
- )
- }
- saveCurrencies(cardId, blockchainNetworks) // migrate saved currencies
- return blockchainNetworks
- }
-
- private suspend fun getTokensIds(tokens: List): Map = coroutineScope {
- tokens.map {
- async {
- tangemNetworkService.getTokens(
- contractAddress = it.contractAddress,
- networkId = it.blockchainDao.toBlockchain().toNetworkId(),
- active = true,
- )
- }
- }.map { it.await() }
- .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
- .mapIndexedNotNull { index, id ->
- if (id == null) null else tokens[index].contractAddress to id
- }.toMap()
- }
-
- fun saveCurrencies(cardId: String, currencies: List) {
- val json = blockchainNetworkAdapter.toJson(currencies)
- context.rewriteFile(json, getFileNameForBlockchains(cardId))
- }
-
- private fun Context.readFileText(fileName: String): String =
- this.openFileInput(fileName).bufferedReader().readText()
-
- private fun Context.rewriteFile(content: String, fileName: String) {
- this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
- it.write(content.toByteArray(), 0, content.length)
- }
- }
-
- fun getTestnetCoins(): List {
- val json = context.assets.readJsonFileToString(FILE_NAME_TESTNET_COINS)
- return currenciesAdapter.fromJson(json)!!.coins
- .map { Currency.fromJsonObject(it) }
- }
-
- private fun loadTokensJson(blockchain: Blockchain): String? {
- val fileName = getFileName(blockchain)
- return try {
- context.assets.readJsonFileToString(fileName)
- } catch (ex: Exception) {
- Timber.e(ex, "Tokens with the file name %s not found", fileName)
- null
- }
- }
-
- private fun getFileName(blockchain: Blockchain): String {
- return StringBuilder().apply {
- append(blockchain.id.lowercase(Locale.getDefault()).replace("/test", ""))
- append("_tokens")
- appendIf("_testnet") { blockchain.isTestnet() }
- }.toString()
- }
-
- private fun fromJsonToTokensDao(tokenJson: String, blockchain: Blockchain): List {
- return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) }
- }
+object CurrenciesRepository {
fun getBlockchains(
cardFirmware: FirmwareVersion,
- isTestNet: Boolean = false
+ isTestNet: Boolean = false,
): List {
val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
Blockchain.secp256k1Blockchains(isTestNet)
@@ -261,21 +26,6 @@ class CurrenciesRepository(
)
}
}
-
- companion object {
- private const val FILE_NAME_PREFIX_TOKENS = "tokens"
- private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
- private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
-
- fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
- fun getFileNameForBlockchains(cardId: String): String =
- "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
- }
}
-fun Blockchain.getTokensName(): String {
- return when (this) {
- Blockchain.Fantom -> "Fantom Opera"
- else -> this.fullName
- }
-}
+
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt
index 5b806beecc..7df68e80c3 100644
--- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt
@@ -1,29 +1,36 @@
package com.tangem.tap.domain.tokens
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.getListOfCoins
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.network.api.tangemTech.CoinsResponse
import com.tangem.network.api.tangemTech.TangemTechService
+import com.tangem.network.common.MoshiConverter
+import com.tangem.tap.common.AssetReader
class LoadAvailableCoinsService(
private val networkService: TangemTechService,
- private val currenciesRepository: CurrenciesRepository
+ private val assetReader: AssetReader,
) {
+ private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() }
+ private val currenciesAdapter: JsonAdapter =
+ moshi.adapter(CurrenciesFromJson::class.java)
suspend fun getSupportedTokens(
isTestNet: Boolean = false,
supportedBlockchains: List,
page: Int,
- searchInput: String? = null
+ searchInput: String? = null,
): Result {
if (isTestNet) {
return Result.Success(
LoadedCoins(
- currencies = currenciesRepository.getTestnetCoins().filter(searchInput),
+ currencies = getTestnetCoins().filter(searchInput),
moreAvailable = false,
- )
+ ),
)
}
val offset = page * LOAD_PER_PAGE
@@ -56,21 +63,28 @@ class LoadAvailableCoinsService(
active = true,
offset = offset,
limit = LOAD_PER_PAGE,
- searchText = searchInput
+ searchText = searchInput,
)
}
+ fun getTestnetCoins(): List {
+ val json = assetReader.readAssetAsString(FILE_NAME_TESTNET_COINS)
+ return currenciesAdapter.fromJson(json)!!.coins
+ .map { Currency.fromJsonObject(it) }
+ }
+
private fun List.filter(searchInput: String?): List {
if (searchInput.isNullOrBlank()) return this
- return filter{
+ return filter {
it.symbol.contains(searchInput, ignoreCase = true) ||
- it.name.contains(searchInput, ignoreCase = true)
+ it.name.contains(searchInput, ignoreCase = true)
}
}
companion object {
const val LOAD_PER_PAGE = 100
+ private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt
new file mode 100644
index 0000000000..7418891662
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt
@@ -0,0 +1,130 @@
+package com.tangem.tap.domain.tokens
+
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Types
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.common.services.Result
+import com.tangem.domain.common.extensions.getTokens
+import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.network.api.tangemTech.TangemTechService
+import com.tangem.network.common.MoshiConverter
+import com.tangem.tap.common.FileReader
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.domain.tokens.models.TokenDao
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+@Deprecated("Use this only for migration")
+class OldUserTokensRepository(
+ private val fileReader: FileReader,
+ private val tangemNetworkService: TangemTechService,
+) {
+ private val moshi = MoshiConverter.defaultMoshi()
+ private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, Blockchain::class.java),
+ )
+ private val tokensAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, TokenDao::class.java),
+ )
+ private val blockchainNetworkAdapter: JsonAdapter> =
+ moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
+
+ @Deprecated("Use BlockchainNetwork instead")
+ private fun loadSavedTokens(cardId: String): List {
+ val json = try {
+ fileReader.readFile(getFileNameForTokens(cardId))
+ } catch (exception: Exception) {
+ return emptyList()
+ }
+
+ return try {
+ tokensAdapter.fromJson(json) ?: emptyList()
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ @Deprecated("Use BlockchainNetwork instead")
+ private fun loadSavedBlockchains(cardId: String): List {
+ return try {
+ val json = fileReader.readFile(getFileNameForBlockchains(cardId))
+ blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ @Deprecated("Use TokensRepository instead")
+ suspend fun loadSavedCurrencies(
+ cardId: String,
+ isHdWalletSupported: Boolean = false,
+ ): List {
+ return try {
+ val json = fileReader.readFile(getFileNameForBlockchains(cardId))
+ blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
+ } catch (exception: Exception) {
+ tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
+ }
+ }
+
+ private suspend fun tryToLoadPreviousFormatAndMigrate(
+ cardId: String,
+ isHdWalletSupported: Boolean = false,
+ ): List {
+ return try {
+ loadSavedCurrenciesOldWay(
+ cardId,
+ isHdWalletSupported,
+ )
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ private suspend fun loadSavedCurrenciesOldWay(
+ cardId: String, isHdWalletSupported: Boolean = false,
+ ): List {
+ val blockchains = loadSavedBlockchains(cardId)
+ val tokens = loadSavedTokens(cardId)
+ val ids = getTokensIds(tokens)
+ val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
+ val blockchainNetworks = blockchains.map { blockchain ->
+ BlockchainNetwork(
+ blockchain = blockchain,
+ derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
+ tokens = tokens
+ .filter { it.blockchainDao.toBlockchain() == blockchain }
+ .map {
+ val token = it.toToken()
+ token.copy(id = ids[token.contractAddress])
+ },
+ )
+ }
+ return blockchainNetworks
+ }
+
+ private suspend fun getTokensIds(tokens: List): Map = coroutineScope {
+ tokens.map {
+ async {
+ tangemNetworkService.getTokens(
+ contractAddress = it.contractAddress,
+ networkId = it.blockchainDao.toBlockchain().toNetworkId(),
+ active = true,
+ )
+ }
+ }.map { it.await() }
+ .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
+ .mapIndexedNotNull { index, id ->
+ if (id == null) null else tokens[index].contractAddress to id
+ }.toMap()
+ }
+
+ companion object {
+ private const val FILE_NAME_PREFIX_TOKENS = "tokens"
+ private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
+ private fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
+ private fun getFileNameForBlockchains(cardId: String): String =
+ "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt
new file mode 100644
index 0000000000..ad188c6913
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.domain.tokens
+
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.services.Result
+import com.tangem.network.api.tangemTech.TangemTechService
+import com.tangem.network.api.tangemTech.UserTokensResponse
+import com.tangem.tap.domain.NoDataError
+import com.tangem.tap.features.wallet.models.Currency
+
+class UserTokensNetworkService(private val tangemTechService: TangemTechService) {
+ suspend fun getUserTokens(userId: String): Result {
+ return when (val result = tangemTechService.getUserTokens(userId)) {
+ is Result.Success -> result
+ is Result.Failure -> {
+ val error = result.error
+ if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) {
+ return Result.Failure(NoDataError(error.customMessage))
+ } else {
+ return result
+ }
+ }
+ }
+ }
+
+ suspend fun saveUserTokens(userId: String, tokens: List): Result {
+ val tokensResponse = tokens.map { it.toTokenResponse() }
+ val data = UserTokensResponse(tokens = tokensResponse)
+ return tangemTechService.putUserTokens(userId, data)
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt
new file mode 100644
index 0000000000..e418215ed7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt
@@ -0,0 +1,117 @@
+package com.tangem.tap.domain.tokens
+
+import android.content.Context
+import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.common.card.Card
+import com.tangem.common.extensions.calculateSha256
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
+import com.tangem.domain.common.extensions.calculateHmacSha256
+import com.tangem.network.api.tangemTech.TangemTechService
+import com.tangem.tap.common.AndroidFileReader
+import com.tangem.tap.domain.NoDataError
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.demo.DemoHelper
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.features.wallet.models.toBlockchainNetworks
+import com.tangem.tap.features.wallet.models.toCurrencies
+import com.tangem.tap.network.NetworkConnectivity
+import com.tangem.tap.store
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class UserTokensRepository(
+ private val storageService: UserTokensStorageService,
+ private val networkService: UserTokensNetworkService,
+) {
+ suspend fun getUserTokens(card: Card): List {
+ if (DemoHelper.isDemoCardId(card.cardId)) {
+ return loadDemoCurrencies()
+ }
+ val userId = card.getUserId()
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ return loadTokensOffline(card, userId)
+ }
+
+ return when (val networkResult = networkService.getUserTokens(userId)) {
+ is Result.Success -> {
+ val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) }
+ storageService.saveUserTokens(card.getUserId(), tokens)
+ tokens
+ }
+ is Result.Failure -> {
+ handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error)
+ }
+ }
+ }
+
+ suspend fun saveUserTokens(card: Card, tokens: List) {
+ networkService.saveUserTokens(card.getUserId(), tokens)
+ storageService.saveUserTokens(card.getUserId(), tokens)
+ }
+
+ suspend fun removeUserTokens(card: Card) {
+ networkService.saveUserTokens(card.getUserId(), emptyList())
+ storageService.saveUserTokens(card.getUserId(), emptyList())
+ }
+
+ fun loadBlockchainsToDerive(card: Card): List {
+ return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: emptyList()
+ }
+
+ private fun loadDemoCurrencies(): List {
+ return DemoHelper.config.demoBlockchains.map {
+ BlockchainNetwork(
+ blockchain = it,
+ derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
+ tokens = emptyList(),
+ )
+ }.flatMap { it.toCurrencies() }
+ }
+
+ private suspend fun handleGetUserTokensFailure(
+ card: Card,
+ userId: String,
+ error: Throwable,
+ ): List {
+ return when (error) {
+ is NoDataError -> {
+ val tokens = storageService.getUserTokens(card)
+ coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } }
+ tokens
+ }
+ else -> {
+ val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
+ tokens
+ }
+ }
+ }
+
+ private suspend fun loadTokensOffline(card: Card, userId: String): List {
+ return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
+ }
+
+ private fun Card.getUserId(): String {
+ val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
+ return calculateUserId(walletPublicKey)
+ }
+
+ private fun calculateUserId(walletPublicKey: ByteArray): String {
+ val message = MESSAGE.toByteArray()
+ val keyHash = walletPublicKey.calculateSha256()
+ return message.calculateHmacSha256(keyHash).toHexString()
+ }
+
+ companion object {
+ const val MESSAGE = "UserWalletID"
+ fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository {
+ val fileReader = AndroidFileReader(context)
+ val oldUserTokensRepository = OldUserTokensRepository(
+ fileReader, store.state.domainNetworks.tangemTechService,
+ )
+ val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader)
+ val networkService = UserTokensNetworkService(tangemTechService)
+ return UserTokensRepository(storageService, networkService)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt
new file mode 100644
index 0000000000..f2011cbebb
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt
@@ -0,0 +1,48 @@
+package com.tangem.tap.domain.tokens
+
+import com.squareup.moshi.JsonAdapter
+import com.tangem.Log
+import com.tangem.common.card.Card
+import com.tangem.network.api.tangemTech.UserTokensResponse
+import com.tangem.network.common.MoshiConverter
+import com.tangem.tap.common.FileReader
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.features.wallet.models.toCurrencies
+
+class UserTokensStorageService(
+ private val oldUserTokensRepository: OldUserTokensRepository,
+ private val fileReader: FileReader,
+) {
+ private val moshi = MoshiConverter.defaultMoshi()
+ private val userTokensAdapter: JsonAdapter =
+ moshi.adapter(UserTokensResponse::class.java)
+
+ fun getUserTokens(userId: String): List? {
+ return try {
+ val json = fileReader.readFile(getFileNameForUserTokens(userId))
+ userTokensAdapter.fromJson(json)?.tokens?.map { Currency.fromTokenResponse(it) }
+ } catch (exception: Exception) {
+ Log.error { exception.stackTraceToString() }
+ null
+ }
+ }
+
+ @Deprecated("")
+ suspend fun getUserTokens(card: Card): List {
+ val blockchainNetworks =
+ oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
+ return blockchainNetworks.flatMap { it.toCurrencies() }
+ }
+
+ fun saveUserTokens(userId: String, tokens: List) {
+ val tokensResponse = tokens.map { it.toTokenResponse() }
+ val data = UserTokensResponse(tokens = tokensResponse)
+ val json = userTokensAdapter.toJson(data)
+ fileReader.rewriteFile(json, getFileNameForUserTokens(userId))
+ }
+
+ companion object {
+ private const val FILE_NAME_PREFIX_USER_TOKENS = "user_tokens"
+ private fun getFileNameForUserTokens(userId: String): String = "${FILE_NAME_PREFIX_USER_TOKENS}_$userId"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
index 45c3dee2e3..de71029d2c 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
@@ -7,6 +7,8 @@ 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
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.walletconnect.extensions.isDappSupported
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
@@ -57,7 +59,15 @@ class WalletConnectManager {
private var sessions: MutableMap = mutableMapOf()
fun connect(wcUri: String) {
- val session = WCSession.from(wcUri) ?: return
+ val session = WCSession.from(wcUri).guard {
+ store.dispatchOnMain(
+ WalletConnectAction.FailureEstablishingSession(
+ session = null,
+ error = TapError.WalletConnect.UnsupportedLink,
+ ),
+ )
+ return
+ }
if (sessions[session] != null) {
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
return
@@ -65,7 +75,19 @@ class WalletConnectManager {
val client = WCClient(httpClient = okHttpClient)
setListeners(client)
val peerId = UUID.randomUUID().toString()
- client.connect(session, tangemPeerMeta, peerId)
+
+ try {
+ client.connect(session, tangemPeerMeta, peerId)
+ } catch (exception: IllegalArgumentException) {
+ store.dispatchOnMain(
+ WalletConnectAction.FailureEstablishingSession(
+ session = null,
+ error = TapError.WalletConnect.UnsupportedLink,
+ ),
+ )
+ return
+ }
+
sessions[session] = WalletConnectActiveData(
peerId = peerId,
remotePeerId = null,
@@ -331,21 +353,30 @@ class WalletConnectManager {
val session = client.session
val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
if (data != null && session != null) {
- sessions[session] = data
- val sessionData = data.toWalletConnectSession()
- sessionData?.let {
+ if (!peer.isDappSupported()) {
store.dispatchOnMain(
- WalletConnectAction.ScanCard(
- session = sessionData,
- chainId = client.chainId?.toIntOrNull()
+ WalletConnectAction.FailureEstablishingSession(
+ session = session,
+ error = TapError.WalletConnect.UnsupportedDapp,
+ ),
+ )
+ } else {
+ sessions[session] = data
+ val sessionData = data.toWalletConnectSession()
+ sessionData?.let {
+ store.dispatchOnMain(
+ WalletConnectAction.ScanCard(
+ session = sessionData,
+ chainId = client.chainId?.toIntOrNull(),
+ ),
)
+ }
+ store.state.globalState.analyticsHandlers?.logWcEvent(
+ Analytics.WcAnalyticsEvent.Session(
+ Analytics.WcSessionEvent.Connect, peer.url,
+ ),
)
}
- store.state.globalState.analyticsHandlers?.logWcEvent(
- Analytics.WcAnalyticsEvent.Session(
- Analytics.WcSessionEvent.Connect, peer.url
- )
- )
}
}
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
deleted file mode 100644
index 144f3b873d..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.tangem.tap.domain.walletconnect
-
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.WalletManager
-import com.tangem.blockchain.common.WalletManagerFactory
-import com.tangem.domain.common.ScanResponse
-import com.tangem.domain.common.TapWorkarounds.isTestCard
-import com.tangem.tap.domain.extensions.makeWalletManagerForApp
-import com.tangem.tap.domain.tokens.CurrenciesRepository
-import com.tangem.tap.domain.tokens.models.BlockchainNetwork
-import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
-import com.tangem.tap.features.wallet.redux.WalletState
-
-class WcWalletManagerFactory(
- private val factory: WalletManagerFactory,
- private val currenciesRepository: CurrenciesRepository,
-) {
- 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(),
- )
- return walletState.getWalletManager(blockchainNetwork)
- }
-
- suspend fun getWalletManager(
- scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState,
- ): WalletManager? {
- val card = scanResponse.card
- val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
- Blockchain.EthereumTestnet
- } else {
- blockchain
- }
- val blockchainNetwork = BlockchainNetwork(
- blockchain = blockchainToMake,
- card = card,
- )
-
- return if (walletState.cardId == card.cardId) {
- walletState.getWalletManager(blockchainNetwork)
- } else {
- if (currenciesRepository
- .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
- .contains(blockchainNetwork)
- ) {
- factory.makeWalletManagerForApp(
- scanResponse = scanResponse,
- blockchainNetwork = blockchainNetwork,
- )
- } else {
- null
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt
new file mode 100644
index 0000000000..057caf7bd2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt
@@ -0,0 +1,9 @@
+package com.tangem.tap.domain.walletconnect.extensions
+
+import com.trustwallet.walletconnect.models.WCPeerMeta
+
+fun WCPeerMeta.isDappSupported(): Boolean {
+ return !unsupportedDappsList.any { this.url.contains(it) }
+}
+
+private val unsupportedDappsList: List = listOf("dydx.exchange")
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
index 650ce5ea09..b0d473cde0 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
@@ -448,6 +448,33 @@ class DemoConfig {
"AB02000000049533",
"AB02000000049541",
"AB02000000049830",
+ // === more cids ===
+ "AC03000000091418",
+ "AC03000000091400",
+ "AC03000000099007",
+ "AC03000000098991",
+ "AC03000000098942",
+ "AC03000000091715",
+ "AC03000000091301",
+ "AC03000000091343",
+ "AB01000000055705",
+ "AB01000000052918",
+ "AB01000000047710",
+ "AB01000000052306",
+ "AB01000000047645",
+ "AB01000000048957",
+ "AB01000000052900",
+ "AB01000000050391",
+ "AB01000000047363",
+ "AB02000000053998",
+ "AB02000000019809",
+ "AB02000000020872",
+ "AB02000000022027",
+ "AB02000000058955",
+ "AB02000000053253",
+ "AB02000000048063",
+ "AB02000000023736",
+ "AB02000000058187",
)
private val testDemoCardIds = listOf(
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index a465940186..fa96a81962 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -11,7 +11,6 @@ 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.currenciesRepository
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@@ -110,11 +109,9 @@ class DetailsMiddleware {
val card = store.state.detailsState.cardSettingsState?.card ?: return
scope.launch {
val result = tangemSdkManager.resetToFactorySettings(card)
- withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
- currenciesRepository.removeCurrencies(card.cardId)
- store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
+ store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
@@ -125,7 +122,6 @@ class DetailsMiddleware {
)
}
}
- }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
index 0258af7316..67b215dd51 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.NotificationAction
+import com.tangem.tap.domain.TapError
import com.tangem.wallet.R
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
@@ -52,7 +53,7 @@ sealed class WalletConnectAction : Action {
val updatedSession: WalletConnectSession,
) : WalletConnectAction()
- data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
+ data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction()
data class SetSessionsRestored(val sessions: List) :
WalletConnectAction()
@@ -103,6 +104,6 @@ sealed class WalletConnectAction : Action {
data class Sign(
val id: Long, val data: ByteArray, val sessionData: WCSession,
- ) : WalletConnectAction()
+ ) : WalletConnectAction()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index 39529bf6f1..ed2c68c8d6 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -2,26 +2,28 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.blockchain.common.WalletManager
import com.tangem.common.card.Card
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.redux.AppDialog
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.currenciesRepository
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
-import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.scope
import com.tangem.tap.store
+import com.tangem.wallet.R
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@@ -96,6 +98,16 @@ class WalletConnectMiddleware {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
}
is WalletConnectAction.FailureEstablishingSession -> {
+ if (action.error != null) {
+ store.dispatch(
+ GlobalAction.ShowDialog(
+ AppDialog.SimpleOkDialogRes(
+ headerId = R.string.common_warning,
+ messageId = action.error.messageResource,
+ ),
+ ),
+ )
+ }
if (action.session != null) {
walletConnectManager.disconnect(action.session)
}
@@ -189,15 +201,10 @@ class WalletConnectMiddleware {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
return
}
- val factory = WcWalletManagerFactory(
- factory = store.state.globalState.tapWalletManager.walletManagerFactory,
- currenciesRepository = currenciesRepository,
- )
- val walletState = store.state.walletState
- val walletManager = factory.getWalletManager(
+ val walletManager = getWalletManager(
wallet = action.session.wallet,
blockchain = blockchain,
- walletState = walletState,
+ walletState = store.state.walletState,
).guard {
store.dispatchOnMain(
GlobalAction.ShowDialog(
@@ -233,20 +240,10 @@ class WalletConnectMiddleware {
handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
}
- private suspend fun getAvailableBlockchains(card: Card, walletState: WalletState): List {
- return if (walletState.cardId == card.cardId) {
- walletState.currencies.filter {
- it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
- }.map { it.blockchain }
- } else {
- currenciesRepository
- .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
- .filter {
- it.blockchain.isEvm() &&
- it.derivationPath == it.blockchain.derivationPath(card.derivationStyle)?.rawPath
- }
- .map { it.blockchain }
- }
+ private fun getAvailableBlockchains(card: Card, walletState: WalletState): List {
+ return walletState.currencies.filter {
+ it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
+ }.map { it.blockchain }
}
private suspend fun prepareWalletManager(
@@ -256,11 +253,7 @@ class WalletConnectMiddleware {
session: WalletConnectSession,
walletConnectManager: WalletConnectManager,
) {
- val factory = WcWalletManagerFactory(
- factory = store.state.globalState.tapWalletManager.walletManagerFactory,
- currenciesRepository = currenciesRepository,
- )
- val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard {
+ val walletManager = getWalletManager(session.wallet, blockchain, walletState).guard {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
store.dispatchOnMain(
GlobalAction.ShowDialog(
@@ -309,17 +302,29 @@ class WalletConnectMiddleware {
NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
),
)
+ val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
+ store.dispatch(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
+ ),
+ )
+ }
- scope.launch {
- val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
-
- withMainContext {
- store.dispatch(
- GlobalAction.ShowDialog(
- WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
- ),
- )
- }
+ private fun getWalletManager(
+ wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState,
+ ): WalletManager? {
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
+ Blockchain.EthereumTestnet
+ } else {
+ blockchain
}
+ val derivation = blockchainToMake.derivationPath(store.state.globalState.scanResponse?.card?.derivationStyle)
+ ?.rawPath
+ val blockchainNetwork = BlockchainNetwork(
+ blockchain = blockchainToMake,
+ derivationPath = derivation,
+ tokens = emptyList(),
+ )
+ return walletState.getWalletManager(blockchainNetwork)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt
index 24129ec514..e0b4f1f186 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -86,7 +87,7 @@ fun WalletConnectDetailsItem(
) {
Row(
modifier = modifier
- .height(84.dp)
+ .defaultMinSize(minHeight = 84.dp)
.fillMaxWidth()
.clickable { onItemsClick(SettingsElement.WalletConnect) },
horizontalArrangement = Arrangement.Start,
@@ -99,7 +100,7 @@ fun WalletConnectDetailsItem(
tint = colorResource(id = R.color.all_colors_azure),
)
Column(
- modifier = modifier.height(56.dp),
+ modifier = modifier.defaultMinSize(minHeight = 56.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Center,
) {
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
index eda5e31bcc..6f29a508b1 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
@@ -1,6 +1,8 @@
package com.tangem.tap.features.details.ui.resetcard
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -89,11 +91,14 @@ fun ResetCardView(
color = colorResource(id = R.color.text_secondary),
)
- Spacer(modifier = modifier.size(44.dp))
+ Spacer(modifier = modifier.size(28.dp))
Row(
modifier = modifier
.fillMaxWidth()
- .padding(end = 20.dp),
+ .clickable(
+ onClick = { state.onAcceptWarningToggleClick(!state.accepted) },
+ )
+ .padding(top = 16.dp, bottom = 16.dp),
) {
IconToggleButton(
checked = state.accepted,
@@ -120,13 +125,15 @@ fun ResetCardView(
text = stringResource(id = R.string.reset_card_to_factory_warning_message),
style = TangemTypography.body2,
color = colorResource(id = R.color.text_secondary),
+ modifier = modifier
+ .padding(end = 20.dp),
)
}
- Spacer(modifier = modifier.size(32.dp))
+ Spacer(modifier = modifier.size(16.dp))
Box(
modifier = modifier
- .padding(start = 16.dp, end = 16.dp),
+ .padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
) {
DetailsMainButton(
title = stringResource(id = R.string.reset_card_to_factory_button_title),
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt
index 8474878afc..168bd4a024 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt
@@ -140,13 +140,12 @@ private fun WalletConnectSessions(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
- Column(modifier = modifier) {
- Text(
- text = session.description,
- style = TangemTypography.subtitle1,
- color = colorResource(id = R.color.text_primary_1),
- )
- }
+ Text(
+ text = session.description,
+ style = TangemTypography.subtitle1,
+ color = colorResource(id = R.color.text_primary_1),
+ modifier = modifier.weight(1f),
+ )
IconButton(
onClick = { state.onRemoveSession(session.sessionId) },
) {
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
index 644f172e29..bd208b2baf 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
@@ -79,41 +79,46 @@ private fun handleReadCard() {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
} else {
changeButtonState(ButtonState.PROGRESS)
- store.dispatch(GlobalAction.ScanCard(onSuccess = { scanResponse ->
- store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
- store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
+ store.dispatch(
+ GlobalAction.ScanCard(
+ onSuccess = { scanResponse ->
+ store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
+ store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
- // TODO: SaltPay: temporary excluding backup process for Visa cards
- if (scanResponse.card.isSaltPayVisa) {
- scope.launch {
- store.onCardScanned(scanResponse)
- withMainContext {
- navigateTo(AppScreen.Wallet, null)
+ // TODO: SaltPay: temporary excluding backup process for Visa cards
+ if (scanResponse.card.isSaltPayVisa) {
+ scope.launch {
+ store.onCardScanned(scanResponse)
+ withMainContext {
+ navigateTo(AppScreen.Wallet, null)
+ }
+ }
+ return@ScanCard
}
- }
- return@ScanCard
- }
- if (OnboardingHelper.isOnboardingCase(scanResponse)) {
- val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
- store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
- navigateTo(navigateTo)
- } else {
- scope.launch {
- store.onCardScanned(scanResponse)
- withMainContext {
- if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
- store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
- navigateTo(AppScreen.OnboardingTwins)
- } else {
- navigateTo(AppScreen.Wallet, null)
+ if (OnboardingHelper.isOnboardingCase(scanResponse)) {
+ val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
+ store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
+ navigateTo(navigateTo)
+ } else {
+ scope.launch {
+ withMainContext {
+ if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
+ navigateTo(AppScreen.OnboardingTwins)
+ } else {
+ navigateTo(AppScreen.Wallet, null)
+ }
+ }
+ store.onCardScanned(scanResponse)
}
}
- }
- }
- }, onFailure = {
- changeButtonState(ButtonState.ENABLED)
- }))
+ },
+ onFailure = {
+ changeButtonState(ButtonState.ENABLED)
+ },
+ ),
+ )
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
index e470680dbc..02950c3b3a 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
@@ -79,17 +79,21 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
val primaryBlockchain = updatedResponse.getBlockchain()
val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) {
val primaryToken = updatedResponse.getPrimaryToken()
- val blockchainNetwork = BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens(
- listOfNotNull(primaryToken))
+ val blockchainNetwork =
+ BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens(
+ listOfNotNull(primaryToken),
+ )
listOf(blockchainNetwork)
} else {
listOf(
BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card),
- BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card)
+ BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card),
)
}
- store.dispatch(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks))
+ store.dispatch(
+ WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks, updatedResponse.card),
+ )
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
index 0dc7e10566..e7d3a6501c 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
@@ -18,7 +18,6 @@ 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.currenciesRepository
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.twins.TwinCardsManager
@@ -271,7 +270,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
CreateTwinWalletMode.RecreateWallet -> {
- currenciesRepository.removeCurrencies(scanResponse.card.cardId)
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
index 016fee1817..1c41c5e27a 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
@@ -102,8 +102,8 @@ private fun handleWalletAction(action: Action) {
store.dispatchOnMain(
WalletAction.MultiWallet.SaveCurrencies(
blockchainNetworks = blockchainNetworks,
- cardId = result.data.card.cardId
- )
+ card = result.data.card,
+ ),
)
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup())
@@ -286,15 +286,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
backupService.proceedBackup { result ->
when (result) {
is CompletionResult.Success -> {
- val blockchainNetworks = listOf(
- BlockchainNetwork(Blockchain.Bitcoin, result.data),
- BlockchainNetwork(Blockchain.Ethereum, result.data)
- )
- store.dispatchOnMain(
- WalletAction.MultiWallet.SaveCurrencies(
- blockchainNetworks = blockchainNetworks, cardId = result.data.cardId
- )
- )
if (backupService.currentState == BackupService.State.Finished) {
store.dispatchOnMain(BackupAction.FinishBackup)
} else {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 54be369642..defca803e3 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -246,8 +246,7 @@ private fun sendTransaction(
}
is SimpleResult.Failure -> {
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
- wallet = walletManager.wallet,
- host = walletManager.currentHost,
+ walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress,
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
index 2ec703e629..96f742ec97 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
@@ -19,7 +19,8 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.redux.domainStore
import com.tangem.operations.derivation.ExtendedPublicKeysMap
-import com.tangem.tap.*
+import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.assetReader
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@@ -32,6 +33,9 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@@ -77,7 +81,7 @@ class TokensMiddleware {
val loadCoinsService = LoadAvailableCoinsService(
store.state.domainNetworks.tangemTechService,
- currenciesRepository
+ assetReader,
)
scope.launch {
@@ -285,22 +289,28 @@ class TokensMiddleware {
else -> DerivationParams.Custom(derivationPath)
}
}
-
val walletManager = factory.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = currency.blockchain,
- derivationParams = derivationParams
+ derivationParams = derivationParams,
) ?: return@mapNotNull null
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
- WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, walletManager)
+ WalletAction.MultiWallet.AddBlockchain(
+ blockchain = blockchainNetwork,
+ walletManager = walletManager,
+ save = true,
+ )
}
is Currency.Token -> {
val rawDerivationPath = currency.derivationPath
?: currency.blockchain.derivationPath(derivationStyle)?.rawPath
-
val blockchainNetwork =
- BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
- WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork)
+ BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token))
+ WalletAction.MultiWallet.AddToken(
+ token = currency.token,
+ blockchain = blockchainNetwork,
+ save = true,
+ )
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt
index 75f752b278..1500a10a1e 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt
@@ -1,14 +1,18 @@
package com.tangem.tap.features.wallet.models
+import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.blockchain.common.Token
+import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
+import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.features.addCustomToken.CustomCurrency
+import com.tangem.network.api.tangemTech.TokenResponse
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
sealed interface Currency {
-
val coinId: String?
get() = when (this) {
is Blockchain -> blockchain.toCoinId()
@@ -17,24 +21,28 @@ sealed interface Currency {
val blockchain: com.tangem.blockchain.common.Blockchain
val currencySymbol: CryptoCurrencyName
val derivationPath: String?
-
val currencyName: String
get() = when (this) {
is Blockchain -> blockchain.fullName
is Token -> token.name
}
+ val decimals
+ get() = when (this) {
+ is Blockchain -> blockchain.decimals()
+ is Token -> token.decimals
+ }
data class Token(
val token: com.tangem.blockchain.common.Token,
override val blockchain: com.tangem.blockchain.common.Blockchain,
- override val derivationPath: String?
+ override val derivationPath: String?,
) : Currency {
override val currencySymbol = token.symbol
}
data class Blockchain(
override val blockchain: com.tangem.blockchain.common.Blockchain,
- override val derivationPath: String?
+ override val derivationPath: String?,
) : Currency {
override val currencySymbol: CryptoCurrencyName = blockchain.currency
}
@@ -48,24 +56,35 @@ sealed interface Currency {
}
fun isBlockchain(): Boolean = this is Blockchain
-
fun isToken(): Boolean = this is Token
+ fun toTokenResponse(): TokenResponse {
+ return TokenResponse(
+ id = coinId ?: "",
+ networkId = blockchain.toNetworkId(),
+ derivationPath = derivationPath ?: DERIVATION_PATH_RAW_VALUE,
+ name = currencyName,
+ symbol = currencySymbol,
+ decimals = decimals,
+ contractAddress = if (this is Token) token.contractAddress else null,
+ )
+ }
companion object {
+ private const val DERIVATION_PATH_RAW_VALUE = "m/44/0'/0/0"
fun fromBlockchainNetwork(
blockchainNetwork: BlockchainNetwork,
- token: com.tangem.blockchain.common.Token? = null
+ token: com.tangem.blockchain.common.Token? = null,
): Currency {
return if (token != null) {
Token(
token = token,
blockchain = blockchainNetwork.blockchain,
- derivationPath = blockchainNetwork.derivationPath
+ derivationPath = blockchainNetwork.derivationPath,
)
} else {
Blockchain(
blockchain = blockchainNetwork.blockchain,
- derivationPath = blockchainNetwork.derivationPath
+ derivationPath = blockchainNetwork.derivationPath,
)
}
}
@@ -74,7 +93,7 @@ sealed interface Currency {
return when (customCurrency) {
is CustomCurrency.CustomBlockchain -> Blockchain(
blockchain = customCurrency.network,
- derivationPath = customCurrency.derivationPath?.rawPath
+ derivationPath = customCurrency.derivationPath?.rawPath,
)
is CustomCurrency.CustomToken -> Token(
token = customCurrency.token,
@@ -88,8 +107,53 @@ sealed interface Currency {
return Token(
token = tokenWithBlockchain.token,
blockchain = tokenWithBlockchain.blockchain,
- derivationPath = null
+ derivationPath = null,
)
}
+
+ fun fromTokenResponse(tokenResponse: TokenResponse): Currency {
+ val derivationPath = if (tokenResponse.derivationPath == DERIVATION_PATH_RAW_VALUE) {
+ null
+ } else {
+ tokenResponse.derivationPath
+ }
+ return when {
+ tokenResponse.contractAddress != null -> Token(
+ com.tangem.blockchain.common.Token(
+ name = tokenResponse.name,
+ symbol = tokenResponse.symbol,
+ contractAddress = tokenResponse.contractAddress!!,
+ decimals = tokenResponse.decimals,
+ id = tokenResponse.id,
+ ),
+ blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!,
+ derivationPath = derivationPath,
+ )
+ else -> Blockchain(
+ blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!,
+ derivationPath = derivationPath,
+ )
+ }
+ }
}
-}
\ No newline at end of file
+}
+
+fun BlockchainNetwork.toCurrencies(): List {
+ val blockchain = Currency.fromBlockchainNetwork(this)
+ val tokens = this.tokens.map { Currency.fromBlockchainNetwork(this, it) }
+ return listOf(blockchain) + tokens
+}
+
+fun List.toCurrencies(): List {
+ return flatMap { it.toCurrencies() }
+}
+
+fun List.toBlockchainNetworks(): List {
+ return this.filter { it.isBlockchain() }.map { BlockchainNetwork(it.blockchain, it.derivationPath, getTokens(it)) }
+}
+
+private fun List.getTokens(currency: Currency): List {
+ return this
+ .filter { it.isToken() && it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
+ .mapNotNull { if (it is Currency.Token) it.token else null }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt
index eb6c8caf49..3b80a95845 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt
@@ -5,8 +5,7 @@ sealed class WalletWarning(
) {
data class ExistentialDeposit(
val currencyName: String,
- val currencySymbols: String,
- val existentialDepositString: String,
+ val edStringValueWithSymbol: String,
) : WalletWarning(1)
object TransactionInProgress : WalletWarning(10)
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 0d6e8b528a..836d287739 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
@@ -31,7 +31,6 @@ sealed class WalletAction : Action {
data class Failure(val error: TapError) : WalletAction()
}
-
data class LoadWallet(
val blockchain: BlockchainNetwork? = null,
val walletManager: WalletManager? = null
@@ -40,7 +39,7 @@ sealed class WalletAction : Action {
data class NoAccount(
val wallet: Wallet,
val blockchain: BlockchainNetwork,
- val amountToCreateAccount: String
+ val amountToCreateAccount: String,
) : WalletAction()
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
@@ -48,28 +47,31 @@ sealed class WalletAction : Action {
data class SetArtworkId(val artworkId: String?) : WalletAction()
+ sealed class UserTokens : WalletAction() {
+ object Loading : UserTokens()
+ object Loaded : UserTokens()
+ }
sealed class MultiWallet : WalletAction() {
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
data class AddBlockchain(
val blockchain: BlockchainNetwork,
- val walletManager: WalletManager?
+ val walletManager: WalletManager?,
+ val save: Boolean,
) : MultiWallet()
data class AddBlockchains(
- val blockchains: List, val walletManagers: List
+ val blockchains: List, val walletManagers: List, val save: Boolean,
) : MultiWallet()
- data class AddTokens(val tokens: List, val blockchain: BlockchainNetwork) :
+ data class AddTokens(val tokens: List, val blockchain: BlockchainNetwork, val save: Boolean) :
MultiWallet()
- data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
+ data class AddToken(val token: Token, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet()
data class SaveCurrencies(
- val blockchainNetworks: List, val cardId: String? = null
+ val blockchainNetworks: List, val card: Card? = null,
) : MultiWallet()
-// object FindTokensInUse : MultiWallet()
-// object FindBlockchainsInUse : MultiWallet()
data class TokenLoaded(
val amount: Amount,
@@ -82,14 +84,15 @@ sealed class WalletAction : Action {
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
data class RemoveWallet(
val currency: Currency,
- val fromScreen: AppScreen
+ val fromScreen: AppScreen,
) : MultiWallet()
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
data class SetPrimaryToken(val token: Token) : MultiWallet()
-
data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet()
object BackupWallet : MultiWallet()
+ data class AddMissingDerivations(val blockchains: List) : MultiWallet()
+ object ScanToGetDerivations : MultiWallet()
}
sealed class Warnings : WalletAction() {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index 6fb9f9e73b..6219fea552 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -19,9 +19,6 @@ import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
-import com.tangem.tap.features.wallet.models.hasPendingTransactions
-import com.tangem.tap.features.wallet.models.hasSendableAmounts
-import com.tangem.tap.features.wallet.models.isSendableAmount
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
@@ -47,6 +44,8 @@ data class WalletState(
val isTestnet: Boolean = false,
val totalBalance: TotalBalance? = null,
val showBackupWarning: Boolean = false,
+ val missingDerivations: List = emptyList(),
+ val loadingUserTokens: Boolean = false,
) : StateType {
// if you do not delegate - the application crashes on startup,
@@ -131,32 +130,6 @@ data class WalletState(
return walletsData.find { it.currency == selectedCurrency }
}
- fun canBeRemoved(walletData: WalletData?): Boolean {
- if (walletData == null) return false
-
- if (!isPrimaryCurrency(walletData)) {
- val walletManager = getWalletManager(walletData.currency)
- ?: return true
-
- if (walletData.currency is Currency.Blockchain &&
- walletManager.cardTokens.isNotEmpty()
- ) {
- return false
- }
-
- val wallet = walletManager.wallet
-
- return when (walletData.currency) {
- is Currency.Blockchain -> !wallet.hasPendingTransactions() && !wallet.hasSendableAmounts()
- is Currency.Token -> {
- val token = walletData.currency.token
- !wallet.hasPendingTransactions(token) && !wallet.isSendableAmount(token)
- }
- }
- }
- return false
- }
-
private fun isPrimaryCurrency(walletData: WalletData): Boolean {
return (walletData.currency is Currency.Blockchain &&
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
@@ -220,21 +193,28 @@ data class WalletState(
fun removeWallet(walletData: WalletData?): WalletState {
if (walletData == null) return this
- return if (walletData.currency is Currency.Blockchain) {
- val walletStores = wallets.filterNot {
- it.blockchainNetwork.blockchain == walletData.currency.blockchain
- && it.blockchainNetwork.derivationPath == walletData.currency.derivationPath
+ return when (val currency = walletData.currency) {
+ is Currency.Blockchain -> {
+ val walletStores = wallets.filterNot {
+ it.blockchainNetwork.blockchain == currency.blockchain
+ && it.blockchainNetwork.derivationPath == currency.derivationPath
+ }
+ copy(wallets = walletStores)
+ .updateTotalBalance()
+ .updateProgressState()
+ }
+ is Currency.Token -> {
+ val walletStore = getWalletStore(walletData.currency)
+ val walletDataList = walletStore?.walletsData
+ ?.filterNot { it.currency == walletData.currency }
+ ?: emptyList()
+ val updatedWalletManager = walletStore?.walletManager?.also { it.removeToken(currency.token) }
+ val updatedWalletStore = walletStore?.copy(
+ walletsData = walletDataList,
+ walletManager = updatedWalletManager,
+ )
+ updateWalletStore(updatedWalletStore)
}
- copy(wallets = walletStores)
- .updateTotalBalance()
- .updateProgressState()
- } else {
- val walletStore = getWalletStore(walletData.currency)
- val walletDataList = walletStore?.walletsData
- ?.filterNot { it.currency == walletData.currency }
- ?: emptyList()
- val updatedWalletStore = walletStore?.copy(walletsData = walletDataList)
- updateWalletStore(updatedWalletStore)
}
}
@@ -380,8 +360,7 @@ data class WalletData(
if (existentialDepositString != null) {
val warning = WalletWarning.ExistentialDeposit(
currencyName = currency.currencyName,
- currencySymbols = currency.currencySymbol,
- existentialDepositString = existentialDepositString,
+ edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}",
)
walletWarnings.add(warning)
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
index 4030bae09b..d7e6ceba13 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
@@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
+import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.safeUpdate
@@ -11,18 +12,20 @@ import com.tangem.tap.common.redux.global.GlobalAction
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.TapError
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.features.wallet.models.toCurrencies
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.models.WalletDialog
+import com.tangem.tap.features.wallet.redux.reducers.toWallet
import com.tangem.tap.scope
import com.tangem.tap.store
+import com.tangem.tap.userTokensRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -44,41 +47,47 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.AddToken -> {
- addTokens(listOf(action.token), action.blockchain, walletState, globalState)
+ addTokens(listOf(action.token), action.blockchain, walletState, globalState, action.save)
}
is WalletAction.MultiWallet.AddTokens -> {
- addTokens(action.tokens, action.blockchain, walletState, globalState)
+ addTokens(action.tokens, action.blockchain, walletState, globalState, action.save)
}
is WalletAction.MultiWallet.AddBlockchain -> {
action.walletManager?.let {
handleAddingWalletManagers(globalState, listOf(action.walletManager))
}
+ val currencies: List =
+ (walletState?.currencies ?: emptyList()) + action.blockchain.toCurrencies()
- globalState.scanResponse?.let {
- currenciesRepository.saveUpdatedCurrency(
- cardId = it.card.cardId,
- blockchainNetwork = action.blockchain
- )
+
+ if (action.save && globalState.scanResponse != null) {
+ scope.launch {
+ userTokensRepository.saveUserTokens(
+ card = globalState.scanResponse.card,
+ tokens = currencies,
+ )
+ }
}
+
store.dispatch(
WalletAction.LoadFiatRate(
coinsList = listOf(
Currency.Blockchain(
action.blockchain.blockchain,
- action.blockchain.derivationPath
- )
- )
- )
+ action.blockchain.derivationPath,
+ ),
+ ),
+ ),
)
store.dispatch(
WalletAction.LoadWallet(
- action.blockchain, action.walletManager
- )
+ action.blockchain, action.walletManager,
+ ),
)
}
is WalletAction.MultiWallet.SaveCurrencies -> {
- val cardId = action.cardId ?: globalState.scanResponse?.card?.cardId ?: return
- currenciesRepository.saveCurrencies(cardId, action.blockchainNetworks)
+ val card = action.card ?: globalState.scanResponse?.card ?: return
+ scope.launch { userTokensRepository.saveUserTokens(card, action.blockchainNetworks.toCurrencies()) }
}
is WalletAction.MultiWallet.TryToRemoveWallet -> {
val currency = action.currency
@@ -89,54 +98,44 @@ class MultiWalletMiddleware {
}
if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) {
- store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog(
- currencyTitle = currency.currencyName,
- currencySymbol = currency.currencySymbol
- ))
+ store.dispatchDialogShow(
+ WalletDialog.TokensAreLinkedDialog(
+ currencyTitle = currency.currencyName,
+ currencySymbol = currency.currencySymbol,
+ ),
+ )
} else {
- store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
- currencyTitle = currency.currencyName,
- onOk = {
- store.dispatch(WalletAction.MultiWallet.RemoveWallet(
- currency = currency,
- fromScreen = AppScreen.WalletDetails
- ))
- store.dispatch(NavigationAction.PopBackTo())
- }
- ))
+ store.dispatchDialogShow(
+ WalletDialog.RemoveWalletDialog(
+ currencyTitle = currency.currencyName,
+ onOk = {
+ store.dispatch(
+ WalletAction.MultiWallet.RemoveWallet(
+ currency = currency,
+ fromScreen = AppScreen.WalletDetails,
+ ),
+ )
+ store.dispatch(NavigationAction.PopBackTo())
+ },
+ ),
+ )
}
}
is WalletAction.MultiWallet.RemoveWallet -> {
val currency = action.currency
- val cardId = globalState.scanResponse?.card?.cardId.guard {
- store.dispatchErrorNotification(TapError.UnsupportedState("cardId is NULL"))
+ val card = globalState.scanResponse?.card.guard {
+ store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL"))
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
return
}
-
- when (currency) {
- is Currency.Blockchain -> {
- currenciesRepository.removeBlockchain(
- cardId = cardId,
- blockchainNetwork = BlockchainNetwork(
- blockchain = currency.blockchain,
- derivationPath = currency.derivationPath,
- tokens = emptyList()
- )
- )
- }
- is Currency.Token -> {
- val walletManager = walletState?.getWalletManager(currency)
- if (walletManager != null) {
- walletManager.removeToken(currency.token)
- currenciesRepository.removeToken(
- cardId = cardId,
- token = currency.token,
- blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
- )
- }
- }
+ var currencies = walletState?.currencies ?: emptyList()
+ currencies = currencies.filterNot { it == currency }
+ if (currency.isBlockchain()) {
+ currencies
+ .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
}
+ scope.launch { userTokensRepository.saveUserTokens(card, currencies) }
+
if (action.fromScreen == AppScreen.AddTokens) {
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
}
@@ -148,94 +147,9 @@ class MultiWalletMiddleware {
store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false))
}
}
-// is WalletAction.MultiWallet.FindBlockchainsInUse -> {
-// val scanResponse = globalState.scanResponse ?: return
-// if (scanResponse.supportsHdWallet()) return
-//
-// val cardFirmware = scanResponse.card.firmwareVersion
-// val blockchains = currenciesRepository.getBlockchains(cardFirmware)
-// .filterNot { walletState?.blockchains?.contains(it) == true }
-// .map { BlockchainNetwork(it, null, emptyList()) }
-// val walletManagers =
-// tapWalletManager.walletManagerFactory.makeWalletManagersForApp(
-// scanResponse,
-// blockchains
-// )
-//
-// scope.launch {
-// walletManagers.map { walletManager ->
-// async(Dispatchers.IO) {
-// walletManager.safeUpdate()
-// val wallet = walletManager.wallet
-// val coinAmount = wallet.amounts[AmountType.Coin]?.value
-// if (coinAmount != null && !coinAmount.isZero()) {
-// scope.launch(Dispatchers.Main) {
-// val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
-// if (walletState?.getWalletData(blockchainNetwork) == null) {
-// store.dispatch(WalletAction.MultiWallet.AddBlockchain(
-// blockchainNetwork, walletManager
-// ))
-// store.dispatch(WalletAction.LoadWallet.Success(
-// wallet = wallet,
-// blockchain = blockchainNetwork
-// ))
-// }
-// }
-// }
-// }
-// }
-// }
-// }
-// is WalletAction.MultiWallet.FindTokensInUse -> {
-// val scanResponse = globalState.scanResponse ?: return
-// if (scanResponse.supportsHdWallet()) return
-//
-// val walletFactory = tapWalletManager.walletManagerFactory
-// val card = scanResponse.card
-//
-// val walletManager = walletState?.getWalletManager(
-// Currency.Blockchain(Blockchain.Ethereum, null)
-// )
-// ?: walletFactory.makeWalletManagerForApp(
-// scanResponse,
-// Currency.Blockchain(Blockchain.Ethereum, null)
-// )
-//
-// val tokenFinder = walletManager as? TokenFinder ?: return
-// scope.launch {
-// val result = tokenFinder.findTokens()
-//
-// withContext(Dispatchers.Main) {
-// when (result) {
-// is Result.Success -> {
-// if (result.data.isNotEmpty()) {
-// val blockchainNetwork = BlockchainNetwork(
-// walletManager.wallet.blockchain,
-// walletManager.wallet.publicKey.derivationPath?.rawPath,
-// walletManager.cardTokens.toList()
-// )
-// currenciesRepository.saveUpdatedCurrency(
-// card.cardId,
-// blockchainNetwork
-// )
-// store.dispatch(
-// WalletAction.MultiWallet.AddBlockchain(
-// blockchainNetwork,
-// walletManager
-// )
-// )
-// store.dispatch(
-// WalletAction.MultiWallet.AddTokens(
-// walletManager.cardTokens.toList(),
-// blockchainNetwork
-// )
-// )
-// }
-// }
-// }
-// }
-// }
-// }
+ is WalletAction.MultiWallet.ScanToGetDerivations -> {
+ store.dispatch(WalletAction.Scan)
+ }
}
}
@@ -249,7 +163,7 @@ class MultiWalletMiddleware {
private fun handleAddingWalletManagers(
globalState: GlobalState,
- walletManagers: List
+ walletManagers: List,
) {
globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers)
if (globalState.scanResponse?.isDemoCard() == true) {
@@ -259,50 +173,59 @@ class MultiWalletMiddleware {
private fun addTokens(
tokens: List, blockchainNetwork: BlockchainNetwork,
- walletState: WalletState?, globalState: GlobalState?
+ walletState: WalletState?, globalState: GlobalState?,
+ save: Boolean,
) {
if (tokens.isEmpty()) return
val scanResponse = globalState?.scanResponse ?: return
val wmFactory = globalState.tapWalletManager.walletManagerFactory
+ val walletState = walletState ?: return
+ val walletManager = walletState.getWalletManager(blockchainNetwork)?.also {
+ if (save) {
+ val wallets = tokens.mapNotNull { token -> token.toWallet(walletState, blockchainNetwork) }
+ val currencies = walletState.updateWalletsData(wallets).currencies
+ scope.launch { userTokensRepository.saveUserTokens(scanResponse.card, currencies) }
+ }
+ } ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
+ store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork.updateTokens(tokens), it, save))
+ }
- val walletManager = walletState?.getWalletManager(blockchainNetwork)
- ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
- store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it))
- } ?: return
-
- store.dispatch(WalletAction.LoadFiatRate(coinsList = tokens.map { token ->
- Currency.Token(
- token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
- )
- }))
- if (tokens.isNotEmpty()) walletManager.addTokens(tokens)
-
- currenciesRepository.saveUpdatedCurrency(
- cardId = scanResponse.card.cardId,
- blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
+ store.dispatch(
+ WalletAction.LoadFiatRate(
+ coinsList = tokens.map { token ->
+ Currency.Token(
+ token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath,
+ )
+ },
+ ),
)
+ if (tokens.isNotEmpty()) walletManager?.addTokens(tokens)
+
scope.launch {
- when (val result = walletManager.safeUpdate()) {
- is com.tangem.common.services.Result.Success -> {
- val wallet = result.data
- wallet.getTokens()
- .filter { tokens.contains(it) }
- .mapNotNull { token ->
- wallet.getTokenAmount(token)?.let { Pair(token, it) }
- }
- .forEach {
- withContext(Dispatchers.Main) {
- store.dispatch(
- WalletAction.MultiWallet.TokenLoaded(
- it.second,
- it.first,
- blockchainNetwork
- )
- )
+ val result = walletManager?.safeUpdate()
+ withMainContext {
+ when (result) {
+ is com.tangem.common.services.Result.Success -> {
+ val wallet = result.data
+ wallet.getTokens()
+ .filter { tokens.contains(it) }
+ .mapNotNull { token ->
+ wallet.getTokenAmount(token)?.let { Pair(token, it) }
}
- }
+ .forEach {
+ withContext(Dispatchers.Main) {
+ store.dispatch(
+ WalletAction.MultiWallet.TokenLoaded(
+ it.second,
+ it.first,
+ blockchainNetwork,
+ ),
+ )
+ }
+ }
+ }
+ else -> {}
}
- is com.tangem.common.services.Result.Failure -> {}
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
index b54a5168a9..19d4a51cf0 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -108,10 +108,13 @@ class WalletMiddleware {
val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
if (coinAmount != null && !coinAmount.isZero()) {
if (walletState.getWalletData(action.blockchain) == null) {
- store.dispatch(WalletAction.MultiWallet.AddBlockchain(
- action.blockchain,
- null
- ))
+ store.dispatch(
+ WalletAction.MultiWallet.AddBlockchain(
+ action.blockchain,
+ null,
+ true,
+ ),
+ )
store.dispatch(WalletAction.LoadWallet.Success(
action.wallet,
action.blockchain
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
index 9821f1f6a7..892b5db149 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
@@ -34,11 +34,10 @@ class MultiWalletReducer {
val walletManager = action.walletManagers.firstOrNull {
it.wallet.blockchain == blockchain.blockchain &&
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
- } ?: return@mapNotNull null
-
- val wallet = walletManager.wallet
+ }
+ val wallet = walletManager?.wallet
val cardToken = if (!state.isMultiwalletAllowed) {
- wallet.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
+ wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
} else {
null
}
@@ -66,7 +65,7 @@ class MultiWalletReducer {
}
val selectedCurrency = if (!state.isMultiwalletAllowed) {
- wallets[0].walletsData[0].currency
+ wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency
} else {
state.selectedCurrency
}
@@ -168,9 +167,11 @@ class MultiWalletReducer {
state.copy(primaryToken = action.token)
is WalletAction.MultiWallet.SaveCurrencies -> state
is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(
- showBackupWarning = action.show
+ showBackupWarning = action.show,
)
+ is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains)
is WalletAction.MultiWallet.BackupWallet -> state
+ is WalletAction.MultiWallet.ScanToGetDerivations -> state
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
index 9579f7311e..3a180040a3 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -146,18 +146,19 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
BalanceStatus.Loading
}
if (action.blockchain == null) {
- val wallets = newState.wallets.map {
- it.copy(
- walletsData = it.walletsData.map { walletData ->
+ val wallets = newState.wallets.map { walletStore ->
+ walletStore.copy(
+ walletsData = walletStore.walletsData.map { walletData ->
walletData.copy(
currencyData = walletData.currencyData.copy(
- status = balanceStatus,
+ status =
+ if (walletStore.walletManager != null) balanceStatus else BalanceStatus.Unreachable,
currency = walletData.currencyData.currency,
currencySymbol = walletData.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
)
- }
+ },
)
}
newState = newState.copy(
@@ -314,6 +315,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
is WalletAction.AppCurrencyAction -> {
newState = appCurrencyReducer.reduce(action, newState)
}
+ is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true)
+ is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false)
else -> { /* no-op */
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt
index 6d34bbbf1f..624d34da3e 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt
@@ -18,7 +18,7 @@ class WalletWarningConverter(
is WalletWarning.ExistentialDeposit -> {
context.getString(
R.string.warning_existential_deposit_message,
- message.currencyName, message.currencySymbols, message.existentialDepositString,
+ message.currencyName, message.edStringValueWithSymbol,
)
}
is WalletWarning.BalanceNotEnoughForFee -> {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
index af061d48f1..a688bb9e42 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
@@ -110,8 +110,12 @@ class WalletAdapter
lContent.tvExchangeRate.text = wallet.fiatRateString
?: root.getString(id = R.string.token_item_no_rate)
- cardWallet.setOnClickListener {
- store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
+ if (wallet.walletAddresses != null) {
+ cardWallet.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
+ }
+ } else {
+ cardWallet.setOnClickListener(null)
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
index 19c1eb1c8a..9d4fe68894 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
@@ -13,7 +13,7 @@ import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
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.tokens.CurrenciesRepository
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.redux.ProgressState
@@ -78,13 +78,16 @@ class MultiWalletView : WalletView() {
handleTotalBalance(binding, state.totalBalance)
handleBackupWarning(binding, state.showBackupWarning)
+ handleRescanWarning(binding, state.missingDerivations.isNotEmpty())
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
+ binding.pbLoadingUserTokens.show(state.loadingUserTokens)
+
binding.btnAddToken.setOnClickListener {
val card = store.state.globalState.scanResponse!!.card
store.dispatch(
TokensAction.LoadCurrencies(
- supportedBlockchains = currenciesRepository.getBlockchains(
+ supportedBlockchains = CurrenciesRepository.getBlockchains(
card.firmwareVersion,
card.isTestCard,
),
@@ -113,6 +116,16 @@ class MultiWalletView : WalletView() {
}
}
+ private fun handleRescanWarning(
+ binding: FragmentWalletBinding,
+ showRescanWarning: Boolean,
+ ) = with(binding.lWalletRescanWarning) {
+ root.isVisible = showRescanWarning
+ root.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.ScanToGetDerivations)
+ }
+ }
+
private fun handleTotalBalance(
binding: FragmentWalletBinding,
totalBalance: TotalBalance?,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
index 1e348987fc..e765e738e8 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
@@ -22,6 +22,7 @@ class SaltPaySingleWalletView : WalletView() {
rowButtons.hide()
rvPendingTransaction.hide()
tvTwinCardNumber.hide()
+ pbLoadingUserTokens.hide()
lCardBalance.root.hide()
lAddress.root.hide()
lSingleWalletBalance.root.show()
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
index 9b8a2e759f..a0126391a3 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
+import com.tangem.tap.common.extensions.getQuantityString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
@@ -36,6 +37,7 @@ class SingleWalletView : WalletView() {
rvMultiwallet.hide()
btnAddToken.hide()
rvPendingTransaction.hide()
+ pbLoadingUserTokens.hide()
lCardBalance.root.show()
lAddress.root.show()
lSingleWalletBalance.root.hide()
@@ -85,11 +87,7 @@ class SingleWalletView : WalletView() {
private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
tvTwinCardNumber.show()
- val number = when (cardNumber) {
- TwinCardNumber.First -> 1
- TwinCardNumber.Second -> 2
- }
- tvTwinCardNumber.text = fragment?.getString(R.string.wallet_twins_chip_format, number, 2)
+ tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, 2)
}
if (twinCardsState?.cardNumber == null) {
tvTwinCardNumber.hide()
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
index dec1f05bae..35cc636808 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
@@ -76,7 +76,12 @@ class MercuryoService(
if (!isBuyAllowed()) return false
// blockchains which cant be defined by mercuryo service
- val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum)
+ val unsupportedBlockchains = listOf(
+ Blockchain.Unknown,
+ Blockchain.Binance,
+ Blockchain.Arbitrum,
+ Blockchain.Optimism,
+ )
val blockchain = currency.blockchain
return when (currency) {
diff --git a/app/src/main/res/drawable/ic_ethereumfair_no_color.xml b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml
new file mode 100644
index 0000000000..dd68efa83e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_ethereumfair_round.xml b/app/src/main/res/drawable/ic_ethereumfair_round.xml
new file mode 100644
index 0000000000..b8e6604a7a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ethereumfair_round.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_ethereumpow_no_color.xml b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml
new file mode 100644
index 0000000000..aa369fd0ab
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_ethereumpow_round.xml b/app/src/main/res/drawable/ic_ethereumpow_round.xml
new file mode 100644
index 0000000000..052ac58398
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ethereumpow_round.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_scan_card.xml b/app/src/main/res/drawable/ic_scan_card.xml
new file mode 100644
index 0000000000..e30ad93bea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scan_card.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_tangem_warning.xml b/app/src/main/res/drawable/ic_tangem_warning.xml
new file mode 100644
index 0000000000..aa1e0c73ee
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tangem_warning.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index 8bab86feb8..6b7fb30259 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -92,17 +92,6 @@
app:barrierDirection="bottom"
app:constraint_referenced_ids="iv_card,tv_twin_card_number" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_wallet_rescan_warning.xml b/app/src/main/res/layout/layout_wallet_rescan_warning.xml
new file mode 100644
index 0000000000..c795c2c22a
--- /dev/null
+++ b/app/src/main/res/layout/layout_wallet_rescan_warning.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml
index 5cb1360d71..66998f875e 100644
--- a/app/src/main/res/values-de/strings_blockchain.xml
+++ b/app/src/main/res/values-de/strings_blockchain.xml
@@ -18,10 +18,6 @@
Der Gesamtbetrag geht über die Bilanz hinaus
Die Gebühr geht über die Bilanz hinaus
Minimum balance is %s
- Memo
- Tag
- Invalid Memo. It won\'t be added to the transaction
- Invalid Tag. It won\'t be added to the transaction
- %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.
+ %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.
Destination account is not active. Send %s or more to activate the account.
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml
index 16338c6931..7a4a70126b 100644
--- a/app/src/main/res/values-de/strings_final.xml
+++ b/app/src/main/res/values-de/strings_final.xml
@@ -86,5 +86,13 @@
Reset to Factory Settings
Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
Select network
+ Scan your card
+ To access all the networks you need to scan the card
+ Failed to establish WalletConnect session
+ Memo
+ Tag
+ Invalid Memo. It won\'t be added to the transaction.
+ Invalid Tag. It won\'t be added to the transaction.
+ Connection with this Dapp cannot be established due to its technical implementation.
To start working with the card scan the first card and make a backup
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml
index 5bbb85c2f3..197f42551c 100644
--- a/app/src/main/res/values-fr/strings_blockchain.xml
+++ b/app/src/main/res/values-fr/strings_blockchain.xml
@@ -18,10 +18,6 @@
Réduire de %s XTZ
Non, envoyer toute la somme
Minimum balance is %s
- Memo
- Tag
- Invalid Memo. It won\'t be added to the transaction
- Invalid Tag. It won\'t be added to the transaction
- %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.
+ %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.
Destination account is not active. Send %s or more to activate the account.
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml
index 42c604b593..5e1c532e79 100644
--- a/app/src/main/res/values-fr/strings_final.xml
+++ b/app/src/main/res/values-fr/strings_final.xml
@@ -86,5 +86,13 @@
Reset to Factory Settings
Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
Select network
+ Scan your card
+ To access all the networks you need to scan the card
+ Failed to establish WalletConnect session
+ Memo
+ Tag
+ Invalid Memo. It won\'t be added to the transaction.
+ Invalid Tag. It won\'t be added to the transaction.
+ Connection with this Dapp cannot be established due to its technical implementation.
To start working with the card scan the first card and make a backup
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml
index 775778dbb0..92df7b9703 100644
--- a/app/src/main/res/values-it/strings_blockchain.xml
+++ b/app/src/main/res/values-it/strings_blockchain.xml
@@ -18,10 +18,6 @@
Riduci di %s XTZ
No, invia l\'intero importo
Minimum balance is %s
- Memo
- Tag
- Invalid Memo. It won\'t be added to the transaction
- Invalid Tag. It won\'t be added to the transaction
- %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.
+ %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.
Destination account is not active. Send %s or more to activate the account.
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml
index 42c604b593..5e1c532e79 100644
--- a/app/src/main/res/values-it/strings_final.xml
+++ b/app/src/main/res/values-it/strings_final.xml
@@ -86,5 +86,13 @@
Reset to Factory Settings
Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
Select network
+ Scan your card
+ To access all the networks you need to scan the card
+ Failed to establish WalletConnect session
+ Memo
+ Tag
+ Invalid Memo. It won\'t be added to the transaction.
+ Invalid Tag. It won\'t be added to the transaction.
+ Connection with this Dapp cannot be established due to its technical implementation.
To start working with the card scan the first card and make a backup
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index ca3e7e93f1..38ee22a3ca 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -129,7 +129,6 @@
Tangem Twin
Один кошелек. Две карты.
Одна, которую Вы держите в руках, и вторая карта с номером #%s
- Карта %1d из %2d
Пересоздать Twin кошелек
Условия использования карты
Tangem Twin
diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml
index 61a4c91467..9ccdfd276e 100644
--- a/app/src/main/res/values-ru/strings_blockchain.xml
+++ b/app/src/main/res/values-ru/strings_blockchain.xml
@@ -18,5 +18,6 @@
Уменьшить на %s XTZ
Нет, отправить все
Минимальный баланс: %s
+ Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены.
Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml
index 9dae8e2e27..0f8e928ad5 100644
--- a/app/src/main/res/values-ru/strings_final.xml
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -97,9 +97,13 @@
Сброс к заводским настройкам
Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа.
Выберите сеть
+ Отсканируйте карту
+ Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту
Memo
Tag
Недопустимый Memo. Он не будет добавлен в транзакцию.
Недопустимый Tag. Он не будет добавлен в транзакцию.
+ Не удалось установить сессию WalletConnect
+ Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации.
Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап
diff --git a/app/src/main/res/values-v29/zendesk_styles.xml b/app/src/main/res/values-v29/zendesk_styles.xml
index 628e83211a..764f632e87 100644
--- a/app/src/main/res/values-v29/zendesk_styles.xml
+++ b/app/src/main/res/values-v29/zendesk_styles.xml
@@ -1,7 +1,6 @@
-
-
diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml
index 6a496314b1..8014c2a199 100644
--- a/app/src/main/res/values/strings_blockchain.xml
+++ b/app/src/main/res/values/strings_blockchain.xml
@@ -18,10 +18,6 @@
Reduce by %s XTZ
No, send all
Minimum balance is %s
- Memo
- Tag
- Invalid Memo. It won\'t be added to the transaction
- Invalid Tag. It won\'t be added to the transaction
- %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.
+ %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed.
Destination account is not active. Send %s or more to activate the account.
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml
index b1c5e9121e..306301c3fa 100644
--- a/app/src/main/res/values/strings_final.xml
+++ b/app/src/main/res/values/strings_final.xml
@@ -99,5 +99,13 @@
Reset to Factory Settings
Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
Select network
+ Scan your card
+ To access all the networks you need to scan the card
+ Failed to establish WalletConnect session
+ Memo
+ Tag
+ Invalid Memo. It won\'t be added to the transaction.
+ Invalid Tag. It won\'t be added to the transaction.
+ Connection with this Dapp cannot be established due to its technical implementation.
To start working with the card scan the first card and make a backup
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index afdc44412f..8d6c5017af 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -16,8 +16,6 @@
One wallet. Two cards.
This one that you are holding in your hands and the other one with number #%s.
- Card %1d of %2d
-
Re-create twin wallet
Card terms of use
diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml
index ebb4fcab6b..2513114d19 100644
--- a/app/src/main/res/values/zendesk_styles.xml
+++ b/app/src/main/res/values/zendesk_styles.xml
@@ -1,6 +1,9 @@
-
diff --git a/dependencies.gradle b/dependencies.gradle
index 465ca3866a..b23c84e56f 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,9 +1,8 @@
ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
- // tangem_card_sdk : 'develop-159',
- tangem_card_sdk: '0.0.1',
- tangem_blockchain_sdk: 'develop-122',
+ tangem_card_sdk : 'develop-163',
+ tangem_blockchain_sdk: 'develop-121',
// tangem_blockchain_sdk: '0.0.1',
]
diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
index f8ed090152..35ada82c64 100644
--- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
+++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
@@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"arbitrum-one" -> Blockchain.Arbitrum
- "arbitrum/test" -> Blockchain.ArbitrumTestnet
+ "arbitrum-one/test" -> Blockchain.ArbitrumTestnet
"avalanche", "avalanche-2" -> Blockchain.Avalanche
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
"binancecoin" -> Blockchain.Binance
@@ -38,6 +38,9 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"tron/test" -> Blockchain.TronTestnet
"xrp", "ripple" -> Blockchain.XRP
"xdai" -> Blockchain.Gnosis
+ "ethereum-pow-iou" -> Blockchain.EthereumPow
+ "ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet
+ "ethereumfair" -> Blockchain.EthereumFair
"polkadot" -> Blockchain.Polkadot
"polkadot/test" -> Blockchain.PolkadotTestnet
"kusama" -> Blockchain.Kusama
@@ -87,6 +90,9 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Tron -> "tron"
Blockchain.TronTestnet -> "tron/test"
Blockchain.Gnosis -> "xdai"
+ Blockchain.EthereumPow -> "ethereum-pow-iou"
+ Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test"
+ Blockchain.EthereumFair -> "ethereumfair"
Blockchain.Polkadot -> "polkadot"
Blockchain.PolkadotTestnet -> "polkadot/test"
Blockchain.Kusama -> "kusama"
@@ -94,7 +100,6 @@ fun Blockchain.toNetworkId(): String {
Blockchain.OptimismTestnet -> "optimistic-ethereum/test"
Blockchain.Dash -> "dash"
Blockchain.SaltPay -> "wxdai"
- else -> "unknown" // TODO
}
}
@@ -120,11 +125,13 @@ fun Blockchain.toCoinId(): String {
Blockchain.Tezos -> "tezos"
Blockchain.XRP -> "ripple"
Blockchain.Dogecoin -> "dogecoin"
- Blockchain.Gnosis, Blockchain.SaltPay -> "xdai"
+ Blockchain.Gnosis -> "xdai"
+ Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou"
+ Blockchain.EthereumFair -> "ethereumfair"
Blockchain.Kusama -> "kusama"
Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum"
Blockchain.Dash -> "dash"
+ Blockchain.SaltPay -> "xdai"
Blockchain.Unknown -> "unknown"
- else -> "unknown" // TODO
}
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt b/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt
new file mode 100644
index 0000000000..fe59135906
--- /dev/null
+++ b/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt
@@ -0,0 +1,10 @@
+package com.tangem.domain.common.extensions
+
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+fun ByteArray.calculateHmacSha256(key: ByteArray): ByteArray {
+ val mac: Mac = Mac.getInstance("HmacSHA256")
+ mac.init(SecretKeySpec(key, "HmacSHA256"))
+ return mac.doFinal(this)
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt
index 502452e476..a502ae5041 100644
--- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt
+++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt
@@ -26,7 +26,7 @@ class AddCustomTokenService(
result.data.coins.forEach { coin ->
val networksWithTheSameAddress = coin.networks
.filter { it.contractAddress != null || it.decimalCount != null }
- .filter { it.contractAddress == contractAddress }
+ .filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true }
.filter { supportedTokenNetworkIds.contains(it.networkId) }
if (networksWithTheSameAddress.isNotEmpty()) {
val newToken = coin.copy(networks = networksWithTheSameAddress)
@@ -45,6 +45,5 @@ class AddCustomTokenService(
private fun selectNetworksForSearch(networkId: String?): String {
return networkId ?: supportedTokenNetworkIds.joinToString(",")
-
}
}
\ No newline at end of file
diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
index 1c09b4116a..dfc524e857 100644
--- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
+++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
@@ -5,7 +5,9 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.domain.AddCustomTokenError
-import com.tangem.domain.AddCustomTokenError.Warning.*
+import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken
+import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded
+import com.tangem.domain.AddCustomTokenError.Warning.UnsupportedSolanaToken
import com.tangem.domain.AddCustomTokenException
import com.tangem.domain.DomainDialog
import com.tangem.domain.DomainWrapped
@@ -14,10 +16,40 @@ import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.toNetworkId
-import com.tangem.domain.common.form.*
-import com.tangem.domain.features.addCustomToken.*
-import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
-import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
+import com.tangem.domain.common.form.Field
+import com.tangem.domain.common.form.Form
+import com.tangem.domain.common.form.TokenContractAddressValidator
+import com.tangem.domain.common.form.TokenDecimalsValidator
+import com.tangem.domain.common.form.TokenNameValidator
+import com.tangem.domain.common.form.TokenNetworkValidator
+import com.tangem.domain.common.form.TokenSymbolValidator
+import com.tangem.domain.features.addCustomToken.AddCustomTokenService
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
+import com.tangem.domain.features.addCustomToken.TokenBlockchainField
+import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
+import com.tangem.domain.features.addCustomToken.TokenField
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.FieldError
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Init
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnAddCustomTokenClicked
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnCreate
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnDestroy
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Screen
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.SetFoundTokenInfo
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.UpdateForm
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Warning
+import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState.Companion.createInitialScreenState
import com.tangem.domain.redux.BaseStoreHub
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.ReStoreReducer
@@ -569,12 +601,12 @@ private class AddCustomTokenReducer(
tangemTechService = globalState.networkServices.tangemTechService,
supportedTokenNetworkIds = supportedTokenNetworkIds
)
-
val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain))
state.copy(
cardDerivationStyle = card.derivationStyle,
form = form,
tangemTechServiceManager = tangemTechServiceManager,
+ screenState = createInitialScreenState(card.settings.isHDWalletAllowed),
)
}
is OnDestroy -> {
diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt
index 97c652da19..2ecd595e8e 100644
--- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt
+++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt
@@ -9,9 +9,31 @@ import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.supportedBlockchains
import com.tangem.domain.common.extensions.supportedTokens
-import com.tangem.domain.common.form.*
-import com.tangem.domain.features.addCustomToken.*
-import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
+import com.tangem.domain.common.form.CustomTokenValidator
+import com.tangem.domain.common.form.DataField
+import com.tangem.domain.common.form.FieldDataConverter
+import com.tangem.domain.common.form.FieldId
+import com.tangem.domain.common.form.FieldToJsonConverter
+import com.tangem.domain.common.form.Form
+import com.tangem.domain.common.form.StringIsEmptyValidator
+import com.tangem.domain.common.form.StringIsNotEmptyValidator
+import com.tangem.domain.common.form.TokenContractAddressValidator
+import com.tangem.domain.common.form.TokenDecimalsValidator
+import com.tangem.domain.common.form.TokenNameValidator
+import com.tangem.domain.common.form.TokenNetworkValidator
+import com.tangem.domain.common.form.TokenSymbolValidator
+import com.tangem.domain.features.addCustomToken.AddCustomTokenService
+import com.tangem.domain.features.addCustomToken.CustomCurrency
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
+import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
+import com.tangem.domain.features.addCustomToken.TokenBlockchainField
+import com.tangem.domain.features.addCustomToken.TokenDerivationPathField
+import com.tangem.domain.features.addCustomToken.TokenField
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.state.StringActionStateConverter
import com.tangem.network.api.tangemTech.CoinsResponse
@@ -119,7 +141,7 @@ data class AddCustomTokenState(
formErrors = emptyMap(),
foundToken = null,
warnings = emptySet(),
- screenState = createInitialScreenState(),
+ screenState = createInitialScreenState(card.settings.isHDWalletAllowed),
tangemTechServiceManager = null,
)
}
@@ -223,15 +245,15 @@ data class AddCustomTokenState(
return (listOf(Blockchain.Unknown) + evmBlockchains).sortByName()
}
- private fun createInitialScreenState(): ScreenState {
+ internal fun createInitialScreenState(showDerivationPathField: Boolean = false): ScreenState {
return ScreenState(
contractAddressField = ViewStates.TokenField(),
network = ViewStates.TokenField(),
name = ViewStates.TokenField(isEnabled = false),
symbol = ViewStates.TokenField(isEnabled = false),
decimals = ViewStates.TokenField(isEnabled = false),
- derivationPath = ViewStates.TokenField(),
- addButton = ViewStates.AddButton(isEnabled = false)
+ derivationPath = ViewStates.TokenField(isVisible = showDerivationPathField),
+ addButton = ViewStates.AddButton(isEnabled = false),
)
}
}
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
index 9a4b2cd8b1..4afd9ed2ef 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
@@ -46,5 +46,27 @@ data class CurrenciesResponse(val currencies: List) {
}
data class GeoResponse(
- val code: String
-) : TangemTechResponse
\ No newline at end of file
+ val code: String,
+) : TangemTechResponse
+
+data class UserTokensResponse(
+ val version: Int = 0,
+ val group: String = "",
+ val sort: String = "",
+ val tokens: List = emptyList(),
+) : TangemTechResponse
+
+data class TokenResponse(
+ val id: String,
+ val networkId: String,
+ val derivationPath: String,
+ val name: String,
+ val symbol: String,
+ val decimals: Int,
+ val contractAddress: String?,
+) : TangemTechResponse
+
+data class TangemTechError(
+ val code: Int,
+ val description: String,
+)
\ No newline at end of file
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
index 80558732dd..518edd9fec 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
@@ -1,6 +1,9 @@
package com.tangem.network.api.tangemTech
+import retrofit2.http.Body
import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Path
import retrofit2.http.Query
/**
@@ -29,4 +32,10 @@ interface TangemTechApi {
@GET("geo")
suspend fun geo(): GeoResponse
+
+ @GET("user-tokens/{user-id}")
+ suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse
+
+ @PUT("user-tokens/{user-id}")
+ suspend fun putUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse)
}
\ No newline at end of file
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
index b52b34c013..6137e8dfa0 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
@@ -58,6 +58,15 @@ class TangemTechService(
performRequest { api.currencies() }
}
+ suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) {
+ performRequest { api.getUserTokens(userId) }
+ }
+
+ suspend fun putUserTokens(userId: String, userTokens: UserTokensResponse): Result =
+ withContext(Dispatchers.IO) {
+ performRequest { api.putUserTokens(userId, userTokens) }
+ }
+
fun addHeaderInterceptors(interceptors: List) {
headerInterceptors.removeAll(interceptors)
headerInterceptors.addAll(interceptors)