Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-18 22:29:23 +03:00
commit 8a9422304a
19 changed files with 126 additions and 93 deletions

View file

@ -22,6 +22,7 @@ fun Blockchain.getRoundIconRes(): Int {
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round
else -> R.drawable.ic_tangem_logo
}
}
@ -44,6 +45,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -274,7 +274,7 @@ class TapWalletManager {
val walletManagers = if (
primaryTokens.isNotEmpty() &&
primaryWalletManager != null &&
primaryBlockchain != null
primaryBlockchain != null && primaryBlockchain != Blockchain.Unknown
) {
val blockchainsWithoutPrimary = savedCurrencies.filterNot { it.blockchain == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(
@ -288,7 +288,9 @@ class TapWalletManager {
WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
)
savedCurrencies.map {
dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
if (it.tokens.isNotEmpty()) {
dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
}
}
}
}

View file

@ -35,7 +35,7 @@ data class Currency(
val name: String,
val symbol: String,
val iconUrl: String,
val contracts: List<Contract>?
val contracts: List<Contract>
) {
companion object {
@ -45,9 +45,19 @@ data class Currency(
name = currency.name,
symbol = currency.symbol,
iconUrl = getIconUrl(currency.id),
contracts = currency.contracts?.toContracts(isTestNet)
contracts = prepareListOfContracts(currency.contracts, currency.id, isTestNet)
)
}
private fun prepareListOfContracts(
contractsFromJson: List<ContractFromJson>?,
currencyId: String,
isTestNet: Boolean
): List<Contract> {
val mainNetwork = Contract.fromCurrencyId(currencyId, isTestNet)
val contracts = contractsFromJson?.toContracts(isTestNet) ?: emptyList()
return (listOfNotNull(mainNetwork) + contracts).distinct()
}
}
}
@ -72,6 +82,18 @@ data class Contract(
)
}
fun fromCurrencyId(currencyId: String, isTestNet: Boolean): Contract? {
val networkId = if (isTestNet) currencyId + TESTNET else currencyId
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
return Contract(
networkId = networkId,
blockchain = blockchain,
address = blockchain.currency,
decimalCount = blockchain.decimals(),
iconUrl = getIconUrl(networkId)
)
}
const val TESTNET = "-testnet"
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.ByteArrayKey
@ -68,19 +68,27 @@ class TokensMiddleware {
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
val scanResponse = store.state.globalState.scanResponse ?: return
//TODO: bad things happens.
val currentTokens = store.state.tokensState.addedWallets.toTokens()
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains(
store.state.tokensState.derivationStyle
val currentTokens = store.state.tokensState.addedWallets.toNonCustomTokensWithBlockchains(
scanResponse.card.derivationStyle
)
val currentBlockchains = store.state.tokensState.addedWallets.toNonCustomBlockchains(
scanResponse.card.derivationStyle
)
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it.token) }
val tokensToRemove = currentTokens.filter { token -> !action.addedTokens.any { it.token == token } }
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) }
val tokensToRemove = currentTokens.filter {
token -> !action.addedTokens.any { it.token == token.token }
}
val derivationStyle = scanResponse.card.derivationStyle
removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove)
removeCurrenciesIfNeeded(convertToCurrencies(
blockchains = blockchainsToRemove,
tokens = tokensToRemove,
derivationStyle = derivationStyle
))
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
store.dispatchDebugErrorNotification("Nothing to save")
@ -88,12 +96,11 @@ class TokensMiddleware {
return
}
val derivationStyle = scanResponse.card.derivationStyle
val currencyList = blockchainsToAdd.map {
Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath)
} + tokensToAdd.map {
Currency.Token(it.token, it.blockchain, it.blockchain.derivationPath(derivationStyle)?.rawPath)
}
val currencyList = convertToCurrencies(
blockchains = blockchainsToAdd,
tokens = tokensToAdd,
derivationStyle = derivationStyle
)
if (scanResponse.supportsHdWallet()) {
deriveMissingBlockchains(scanResponse, currencyList) {
submitAdd(it, currencyList)
@ -105,6 +112,22 @@ class TokensMiddleware {
}
}
private fun convertToCurrencies(
blockchains: List<Blockchain>,
tokens: List<TokenWithBlockchain>,
derivationStyle: DerivationStyle?
): List<Currency> {
return blockchains.map {
Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath)
} + tokens.map {
Currency.Token(
it.token,
it.blockchain,
it.blockchain.derivationPath(derivationStyle)?.rawPath
)
}
}
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
currencyList: List<Currency>,
@ -233,17 +256,10 @@ class TokensMiddleware {
addActions.forEach { store.dispatchOnMain(it) }
}
private fun removeCurrenciesIfNeeded(blockchains: List<Blockchain>, tokens: List<Token>) {
if (tokens.isNotEmpty()) {
tokens.forEach { token ->
store.state.walletState.getWalletData(token)?.let {
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
}
}
}
if (blockchains.isNotEmpty()) {
blockchains.forEach { blockchain ->
store.state.walletState.getWalletData(blockchain)?.let {
private fun removeCurrenciesIfNeeded(currencies: List<Currency>) {
if (currencies.isNotEmpty()) {
currencies.forEach { currency ->
store.state.walletState.getWalletData(currency)?.let {
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
}
}

View file

@ -22,15 +22,15 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
is TokensAction.SetAddedCurrencies -> {
tokensState.copy(
addedBlockchains = action.wallets.toBlockchains(action.derivationStyle),
addedTokens = action.wallets.toTokensWithBlockchains(action.derivationStyle),
addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle),
addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle),
addedWallets = action.wallets,
derivationStyle = action.derivationStyle
)
}
is TokensAction.SetNonRemovableCurrencies -> {
tokensState.copy(
nonRemovableBlockchains = action.wallets.toBlockchains(tokensState.derivationStyle),
nonRemovableBlockchains = action.wallets.toNonCustomBlockchains(tokensState.derivationStyle),
nonRemovableTokens = action.wallets.toTokensContractAddresses(),
)
}

View file

@ -27,11 +27,13 @@ fun List<WalletData>.toTokensContractAddresses(): List<ContractAddress> {
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token?.contractAddress }.distinct()
}
fun List<WalletData>.toTokens(): List<Token> {
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }.distinct()
fun List<WalletData>.toNonCustomTokens(derivationStyle: DerivationStyle?): List<Token> {
return filter { !it.currency.isCustomCurrency(derivationStyle) }
.mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }
.distinct()
}
fun List<WalletData>.toTokensWithBlockchains(derivationStyle: DerivationStyle?): List<TokenWithBlockchain> {
fun List<WalletData>.toNonCustomTokensWithBlockchains(derivationStyle: DerivationStyle?): List<TokenWithBlockchain> {
return mapNotNull {
if (it.currency !is com.tangem.tap.features.wallet.redux.Currency.Token) return@mapNotNull null
if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
@ -39,7 +41,7 @@ fun List<WalletData>.toTokensWithBlockchains(derivationStyle: DerivationStyle?):
}.distinct()
}
fun List<WalletData>.toBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
fun List<WalletData>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
return mapNotNull {
if (it.currency.isCustomCurrency(derivationStyle)) {
null
@ -58,7 +60,7 @@ fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency
if (supportedBlockchains == null) return this
return map {
it.copy(contracts =
it.contracts?.filter {
it.contracts.filter {
supportedBlockchains.contains(it.blockchain) && it.blockchain.canHandleTokens()
}
)

View file

@ -59,7 +59,7 @@ fun CollapsedCurrencyItem(
.align(Alignment.CenterVertically)
) {
Text(
text = currency.name,
text = currency.fullName,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1C1C1E),

View file

@ -20,7 +20,6 @@ import androidx.compose.ui.unit.sp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@ -65,7 +64,7 @@ fun ExpandedCurrencyItem(
.align(Alignment.CenterVertically)
) {
Text(
text = currency.name,
text = currency.fullName,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
color = Color(0xFF1C1C1E),
@ -87,9 +86,7 @@ fun ExpandedCurrencyItem(
)
}
val blockchains = currency.contracts?.map { it.blockchain } ?: listOfNotNull(
Blockchain.fromNetworkId(currency.id)
)
val blockchains = currency.contracts.map { it.blockchain }
Row {
Box(
@ -134,7 +131,7 @@ fun ExpandedCurrencyItem(
.padding(top = 6.dp),
) {
blockchains.map { blockchain ->
val contract = currency.contracts?.firstOrNull { it.blockchain == blockchain }
val contract = currency.contracts.firstOrNull { it.blockchain == blockchain }
val added = if (contract != null && contract.address != currency.symbol) {
addedTokens.map { it.token.contractAddress }.contains(contract.address)
} else {

View file

@ -66,4 +66,7 @@ fun ListOfCurrencies(
}
}
}
}
val Currency.fullName: String
get() = "${this.name} (${this.symbol})"

View file

@ -46,7 +46,7 @@ fun NetworkItem(
.combinedClickable(
enabled = allowToAdd,
onLongClick = {
if (contract != null) onNetworkItemClicked(contract.address)
if (!isBlockchain) onNetworkItemClicked(contract!!.address)
},
onClick = {},
indication = null,

View file

@ -5,7 +5,6 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
@ -75,19 +74,9 @@ data class WalletState(
val walletManagers: List<WalletManager>
get() = wallets.mapNotNull { it.walletManager }
fun getWalletManager(token: Token?): WalletManager? {
if (token == null) return null
return wallets
.mapNotNull { it.walletManager }
.find { walletManager ->
walletManager.cardTokens.any { it.contractAddress == token.contractAddress }
}
}
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return wallets.map { it.walletManager }
.find { it?.wallet?.blockchain == currency.blockchain }
return getWalletStore(currency)?.walletManager
}
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
@ -131,22 +120,6 @@ data class WalletState(
return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency }
}
fun getWalletData(token: Token?): WalletData? {
if (token == null) return null
return walletsData.find {
(it.currency as? Currency.Token)?.token == token &&
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
}
}
fun getWalletData(blockchain: Blockchain?): WalletData? {
if (blockchain == null) return null
return walletsData.find {
(it.currency as? Currency.Blockchain)?.blockchain == blockchain &&
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
}
}
fun getSelectedWalletData(): WalletData? {
return walletsData.find { it.currency == selectedCurrency }
}

View file

@ -92,7 +92,7 @@ class MultiWalletMiddleware {
}
}
is Currency.Token -> {
val walletManager = walletState?.getWalletManager(currency.token)
val walletManager = walletState?.getWalletManager(currency)
if (walletManager != null) {
walletManager.removeToken(currency.token)
cardId?.let {
@ -223,6 +223,7 @@ class MultiWalletMiddleware {
tokens: List<Token>, blockchainNetwork: BlockchainNetwork,
walletState: WalletState?, globalState: GlobalState?
) {
if (tokens.isEmpty()) return
val scanResponse = globalState?.scanResponse ?: return
val wmFactory = globalState.tapWalletManager.walletManagerFactory

View file

@ -50,7 +50,7 @@ class TradeCryptoMiddleware {
if (exchangeAction == CurrencyExchangeManager.Action.Buy &&
currency is Currency.Token && currency.blockchain.isTestnet()
) {
val walletManager = store.state.walletState.getWalletManager(currency.token)
val walletManager = store.state.walletState.getWalletManager(currency)
if (walletManager !is EthereumWalletManager) return
scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) }

View file

@ -99,7 +99,8 @@ class MultiWalletReducer {
addTokens(listOf(action.token), action.blockchain, state)
}
is WalletAction.MultiWallet.TokenLoaded -> {
val pendingTransactions = state.getWalletManager(action.token)
val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token)
val pendingTransactions = state.getWalletManager(currency)
?.wallet?.let { wallet ->
wallet.recentTransactions.toPendingTransactions(wallet.address)
} ?: emptyList()
@ -113,7 +114,7 @@ class MultiWalletReducer {
pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
else -> BalanceStatus.VerifiedOnline
}
val tokenWalletData = state.getWalletData(action.token)
val tokenWalletData = state.getWalletData(currency)
val newTokenWalletData = tokenWalletData?.copy(
currencyData = tokenWalletData.currencyData.copy(
status = tokenBalanceStatus,
@ -166,11 +167,10 @@ private fun addTokens(
fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? {
if (!state.isMultiwalletAllowed) return null
if (state.currencies.any { it is Currency.Token && it.token == this }) {
return null
}
val currency = Currency.fromBlockchainNetwork(blockchain, this)
if (state.currencies.contains(currency)) return null
val walletManager = state.getWalletManager(this)?.wallet
val walletManager = state.getWalletManager(currency)?.wallet
val walletAddresses = createAddressList(walletManager)
return WalletData(
@ -181,6 +181,6 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat
),
walletAddresses = walletAddresses,
mainButton = WalletMainButton.SendButton(false),
currency = Currency.fromBlockchainNetwork(blockchain, this)
currency = currency
)
}

View file

@ -74,7 +74,8 @@ class OnWalletLoadedReducer {
)
val tokens = wallet.getTokens().mapNotNull { token ->
val tokenWalletData = walletState.getWalletData(token)
val currency = Currency.fromBlockchainNetwork(blockchainNetwork, token)
val tokenWalletData = walletState.getWalletData(currency)
val tokenPendingTransactions =
pendingTransactions.filter { it.currency == token.symbol }
val tokenBalanceStatus = when {

View file

@ -164,7 +164,10 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
val wallets = newState.updateTradeCryptoState(exchangeManager, newState.replaceSomeWallets(newWallets))
val wallets = newState.updateTradeCryptoState(
exchangeManager,
newState.replaceSomeWallets(newWallets)
)
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
newState = newState.updateWalletStore(walletStore)
}
@ -217,7 +220,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
),
)
val tokenWallets = action.wallet.getTokens()
.mapNotNull { newState.getWalletData(it) }
.mapNotNull { token ->
walletStore?.blockchainNetwork?.let {
newState.getWalletData(Currency.fromBlockchainNetwork(it, token))
}
}
.map {
it.copy(
currencyData = it.currencyData.copy(
@ -288,12 +295,14 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
?: return newState
val address = walletAddresses.list.firstOrNull { it.type == action.type }
?: return newState
newState = newState.updateWalletData(selectedWalletData?.copy(
walletAddresses = WalletAddresses(
address,
walletAddresses.list
newState = newState.updateWalletData(
selectedWalletData?.copy(
walletAddresses = WalletAddresses(
address,
walletAddresses.list
)
)
))
)
}
is WalletAction.SetWalletRent -> {
var walletData = newState.getWalletData(action.blockchain)

View file

@ -115,6 +115,7 @@ class WalletAdapter
private fun toggleWarning(show: Boolean) {
binding.tvExchangeRate.show(!show)
binding.tvCustomCurrency.show(!show)
binding.tvStatusErrorMessage.show(show)
}
}

View file

@ -122,8 +122,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:textColor="@color/darkGray2"
android:textSize="14sp"
android:background="@drawable/shape_rectangle_rounded_4"

View file

@ -4,8 +4,8 @@ import com.tangem.blockchain.common.Blockchain
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"avalanche" -> Blockchain.Avalanche
"avalanche-testnet" -> Blockchain.AvalancheTestnet
"avalanche", "avalanche-2" -> Blockchain.Avalanche
"avalanche-testnet", "avalanche-2-testnet" -> Blockchain.AvalancheTestnet
"binancecoin" -> Blockchain.Binance
"binancecoin-testnet" -> Blockchain.BinanceTestnet
"binance-smart-chain" -> Blockchain.BSC