From f7f2ab7f8866522203519afe15b511a8b4c7cd06 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Jan 2024 22:29:41 +0500 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 22 ++ .../tap/di/domain/TokensDomainModule.kt | 16 ++ .../features/send/redux/SendScreenAction.kt | 6 + .../send/redux/reducers/ReceiptReducer.kt | 190 +++++++++++++++++- .../send/redux/reducers/SendScreenReducer.kt | 30 ++- .../send/redux/states/ReceiptState.kt | 15 +- .../features/send/redux/states/SendState.kt | 12 +- .../stateSubscribers/SendStateSubscriber.kt | 91 ++++++++- .../middlewares/TradeCryptoMiddleware.kt | 8 + core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- .../core/ui/extensions/BlockchainIcons.kt | 5 + .../src/main/res/drawable/ic_vechain_22.xml | 9 + .../src/main/res/drawable/img_vechain_22.xml | 16 ++ .../tangem/data/tokens/di/TokensDataModule.kt | 2 + .../repository/DefaultCurrenciesRepository.kt | 30 +++ .../domain/common/extensions/Blockchain.kt | 13 +- .../DefaultWalletManagersFacade.kt | 29 +++ .../walletmanager/WalletManagersFacade.kt | 11 + .../domain/tokens/model/FeePaidCurrency.kt | 15 ++ .../model/warnings/CryptoCurrencyWarning.kt | 8 + .../tokens/GetCryptoCurrencyActionsUseCase.kt | 49 ++++- .../tokens/GetCurrencyWarningsUseCase.kt | 82 +++++++- ...tFeePaidCryptoCurrencyStatusSyncUseCase.kt | 51 +++++ .../domain/tokens/legacy/TradeCryptoAction.kt | 7 +- .../tokens/repository/CurrenciesRepository.kt | 6 + .../repository/MockCurrenciesRepository.kt | 5 + .../feature/swap/domain/SwapInteractorImpl.kt | 7 +- ...okenDetailsNotificationsAnalyticsSender.kt | 4 +- .../components/TokenDetailsNotification.kt | 50 ++++- .../TokenDetailsNotificationConverter.kt | 26 ++- .../viewmodels/TokenDetailsViewModel.kt | 18 +- .../wallet/viewmodels/WalletViewModel.kt | 47 +++-- .../WalletCurrencyActionsClickIntents.kt | 36 +++- gradle/dependencies.toml | 2 +- 35 files changed, 831 insertions(+), 91 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_vechain_22.xml create mode 100644 core/ui/src/main/res/drawable/img_vechain_22.xml create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/FeePaidCurrency.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 1cfcedb097..cbc9238746 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -402,6 +402,28 @@ } ] }, + { + "id" : "vechain", + "symbol" : "VET", + "name" : "Vechain", + "networks" : [ + { + "networkId" : "vechain/test" + } + ] + }, + { + "id": "vethor-token", + "symbol": "VTHO", + "name": "VeThor", + "networks": [ + { + "networkId": "vechain/test", + "contractAddress": "0x0000000000000000000000000000456E65726779", + "decimalCount": 18 + } + ] + }, { "id" : "tron", "symbol" : "TRX", diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index b07f25f84d..336c1129ed 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -215,6 +215,22 @@ internal object TokensDomainModule { ) } + @Provides + @ViewModelScoped + fun provideGetFeePaidCryptoCurrencyStatusSyncUseCase( + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetFeePaidCryptoCurrencyStatusSyncUseCase { + return GetFeePaidCryptoCurrencyStatusSyncUseCase( + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + dispatchers = dispatchers, + ) + } + @Provides @ViewModelScoped fun provideGetCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 07d0d822b7..08f0c090d5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -3,10 +3,12 @@ package com.tangem.tap.features.send.redux import com.tangem.Message import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.FeePaidCurrency import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.core.TangemSdkError import com.tangem.core.navigation.StateDialog +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError @@ -28,10 +30,14 @@ object ReleaseSendState : Action data class PrepareSendScreen( val walletManager: WalletManager, + val feePaidCurrency: FeePaidCurrency, + val currency: CryptoCurrency, val coinAmount: Amount?, val coinRate: BigDecimal?, val tokenAmount: Amount? = null, val tokenRate: BigDecimal? = null, + val feeCurrencyRate: BigDecimal? = null, + val feeCurrencyDecimals: Int = 0, ) : SendScreenAction // Address diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt index 7d2d583f2f..e842fd4a01 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.FeePaidCurrency import com.tangem.blockchain.common.Wallet import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.tap.common.extensions.scaleToFiat @@ -14,6 +15,7 @@ import java.math.BigDecimal /** [REDACTED_AUTHOR] */ +@Suppress("LargeClass") class ReceiptReducer : SendInternalReducer { private lateinit var sendState: SendState @@ -33,9 +35,10 @@ class ReceiptReducer : SendInternalReducer { private fun handleRefresh(state: ReceiptState): SendState { val wallet = sendState.walletManager?.wallet ?: return sendState + val feePaidCurrency = wallet.blockchain.feePaidCurrency() - val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) - val symbols = determineSymbols(wallet, amountState.typeOfAmount) + val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount, feePaidCurrency) + val symbols = determineSymbols(wallet, amountState.typeOfAmount, feePaidCurrency) val showBlank = !SendState.isReadyToSend() val result = state.copy( visibleTypeOfReceipt = layoutType, @@ -44,6 +47,10 @@ class ReceiptReducer : SendInternalReducer { crypto = createCryptoType(symbols, showBlank), tokenFiat = createTokenFiatType(symbols, showBlank), tokenCrypto = createTokenCryptoType(symbols, showBlank), + customTokenFiat = createCustomTokenFiat(symbols, showBlank), + customTokenCrypto = createCustomTokenCrypto(symbols, showBlank), + sameCurrencyFiat = createSameCurrencyFiat(symbols, showBlank), + sameCurrencyCrypto = createSameCurrencyCrypto(symbols, showBlank), ) return updateLastState(sendState.copy(receiptState = result), result) } @@ -170,7 +177,143 @@ class ReceiptReducer : SendInternalReducer { } } - private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols { + private fun createCustomTokenCrypto(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenCrypto { + val feeValue = feeState.getCurrentFeeValue() + if (showBlank) { + return ReceiptTokenCrypto("0", feeValue.stripZeroPlainString(), "0", symbols) + } + val tokensToSend = amountState.amountToSendCrypto + + return if (sendState.currencyIsConvertible() && sendState.feeIsConvertible()) { + val currencyConverter = when (amountState.typeOfAmount) { + is AmountType.Token -> sendState.tokenConverter!! + else -> sendState.coinConverter!! + } + val currencyFiat = currencyConverter.toFiatUnscaled(tokensToSend) + val feeFiat = sendState.customFeeConverter!!.toFiatUnscaled(feeValue) + val totalFiat = currencyFiat.plus(feeFiat) + + ReceiptTokenCrypto( + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeValue.stripZeroPlainString().addPrecisionSign(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + symbols = symbols, + ) + } else { + ReceiptTokenCrypto( + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeValue.stripZeroPlainString().addPrecisionSign(), + totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + symbols = symbols, + ) + } + } + + private fun createCustomTokenFiat(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenFiat { + val feeCoin = feeState.getCurrentFeeValue() + if (showBlank) { + val feeFiat = convertToFiatPrecision(feeCoin) + return ReceiptTokenFiat("0", feeFiat, "0", "0", "0", symbols) + } + + val tokensToSend = amountState.amountToSendCrypto + + return if (sendState.currencyIsConvertible() && sendState.feeIsConvertible()) { + val currencyConverter = when (amountState.typeOfAmount) { + is AmountType.Token -> sendState.tokenConverter!! + else -> sendState.coinConverter!! + } + val feeFiat = sendState.customFeeConverter!!.toFiatUnscaled(feeCoin) + val amountFiat = currencyConverter.toFiatUnscaled(tokensToSend) + val totalFiat = amountFiat.plus(feeFiat) + ReceiptTokenFiat( + amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(), + feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString().addPrecisionSign(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + willSentToken = tokensToSend.scaleToFiat(true).stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols, + ) + } else { + ReceiptTokenFiat( + amountFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + feeFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + willSentToken = tokensToSend.stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols, + ) + } + } + + private fun createSameCurrencyCrypto(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptCrypto { + val feeCrypto = feeState.getCurrentFeeValue() + val feeFiat = convertToFiatPrecision(feeCrypto) + if (showBlank) { + return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", "0", "0", symbols) + } + val isToken = amountState.typeOfAmount is AmountType.Token + + if (feeState.feeIsIncluded) { + return ReceiptCrypto( + amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString().addPrecisionSign(), + totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(value = amountState.amountToSendCrypto, isToken = isToken), + symbols = symbols, + ) + } else { + val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto) + return ReceiptCrypto( + amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString().addPrecisionSign(), + totalCrypto = totalCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(value = totalCrypto, isToken = isToken), + symbols = symbols, + ) + } + } + + private fun createSameCurrencyFiat(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptFiat { + val feeCrypto = feeState.getCurrentFeeValue() + val isToken = amountState.typeOfAmount is AmountType.Token + val feeFiat = convertToFiatPrecision(feeCrypto, isToken = isToken) + + if (showBlank) { + return ReceiptFiat("0", feeFiat, "0", "0", symbols) + } + + return if (feeState.feeIsIncluded) { + val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto.minus(feeCrypto), isToken = isToken) + val totalFiat = convertToFiatPrecision(amountState.amountToSendCrypto, isToken = isToken) + ReceiptFiat( + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = amountState.amountToSendCrypto.scaleToFiat(true).stripZeroPlainString(), + symbols = symbols, + ) + } else { + val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto) + val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto, isToken = isToken) + val totalFiat = convertToFiatPrecision(totalAmountCrypto, isToken = isToken) + ReceiptFiat( + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = totalAmountCrypto.scaleToFiat().stripZeroPlainString(), + symbols = symbols, + ) + } + } + + private fun determineSymbols( + wallet: Wallet, + amountType: AmountType, + feePaidCurrency: FeePaidCurrency, + ): ReceiptSymbols { return ReceiptSymbols( fiat = store.state.globalState.appCurrency.code, crypto = wallet.blockchain.currency, @@ -178,17 +321,52 @@ class ReceiptReducer : SendInternalReducer { is AmountType.Token -> amountType.token.symbol else -> null }, + fee = when (feePaidCurrency) { + FeePaidCurrency.Coin -> wallet.blockchain.currency + FeePaidCurrency.SameCurrency -> store.state.sendState.currency?.symbol + is FeePaidCurrency.Token -> feePaidCurrency.token.symbol + }, ) } - private fun determineLayoutType(mainCurrencyType: MainCurrencyType, amountType: AmountType): ReceiptLayoutType { + private fun determineLayoutType( + mainCurrencyType: MainCurrencyType, + amountType: AmountType, + feePaidCurrency: FeePaidCurrency, + ): ReceiptLayoutType { return when (mainCurrencyType) { - MainCurrencyType.FIAT -> when (amountType) { + MainCurrencyType.FIAT -> determineFiatLayoutType(feePaidCurrency, amountType) + MainCurrencyType.CRYPTO -> determineCryptoLayoutType(feePaidCurrency, amountType) + } + } + + private fun determineFiatLayoutType(feePaidCurrency: FeePaidCurrency, amountType: AmountType): ReceiptLayoutType { + return when (feePaidCurrency) { + is FeePaidCurrency.Token -> { + val amountToken = (amountType as? AmountType.Token)?.token + val sameToken = + feePaidCurrency.token.contractAddress.equals(amountToken?.contractAddress, ignoreCase = true) + if (sameToken) ReceiptLayoutType.SAME_CURRENCY_FIAT else ReceiptLayoutType.FEE_IN_CUSTOM_TOKEN_FIAT + } + FeePaidCurrency.Coin -> when (amountType) { AmountType.Coin -> ReceiptLayoutType.FIAT is AmountType.Token -> ReceiptLayoutType.TOKEN_FIAT AmountType.Reserve -> ReceiptLayoutType.UNKNOWN } - MainCurrencyType.CRYPTO -> when (amountType) { + FeePaidCurrency.SameCurrency -> ReceiptLayoutType.SAME_CURRENCY_FIAT + } + } + + private fun determineCryptoLayoutType(feePaidCurrency: FeePaidCurrency, amountType: AmountType): ReceiptLayoutType { + return when (feePaidCurrency) { + is FeePaidCurrency.Token -> { + val amountToken = (amountType as? AmountType.Token)?.token + val sameToken = + feePaidCurrency.token.contractAddress.equals(amountToken?.contractAddress, ignoreCase = true) + if (sameToken) ReceiptLayoutType.SAME_CURRENCY else ReceiptLayoutType.FEE_IN_CUSTOM_TOKEN + } + FeePaidCurrency.SameCurrency -> ReceiptLayoutType.SAME_CURRENCY + FeePaidCurrency.Coin -> when (amountType) { AmountType.Coin -> ReceiptLayoutType.CRYPTO is AmountType.Token -> ReceiptLayoutType.TOKEN_CRYPTO AmountType.Reserve -> ReceiptLayoutType.UNKNOWN diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index b8afccf494..f71667eed8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.FeePaidCurrency import com.tangem.tap.common.CurrencyConverter import com.tangem.tap.common.entities.IndeterminateProgressButton @@ -91,29 +90,40 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer { val walletManager = action.walletManager val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!! val decimals = amountToExtract.decimals - val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain) + val canIncludeFee = canIncludeFee( + typeOfAmount = amountToExtract.type, + feePaidCurrency = action.feePaidCurrency, + ) return sendState.copy( walletManager = walletManager, coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, + customFeeConverter = action.feeCurrencyRate?.let { CurrencyConverter(it, action.feeCurrencyDecimals) }, amountState = sendState.amountState.copy( - feePaidInCurrencyNetworkCurrency = feePaidInNetworkCurrency, amountToExtract = amountToExtract, typeOfAmount = amountToExtract.type, balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO, ), - feeState = sendState.feeState.copy( - includeFeeSwitcherIsEnabled = feePaidInNetworkCurrency || isCoinAmount(amountToExtract.type), - ), + feeState = sendState.feeState.copy(includeFeeSwitcherIsEnabled = canIncludeFee), + canIncludeFee = canIncludeFee, + currency = action.currency, ) } - private fun isFeePaidInNetworkCurrency(blockchain: Blockchain): Boolean = - // blockchain.tokenTransactionFeePaidInNetworkCurrency() - blockchain.feePaidCurrency() == FeePaidCurrency.SameCurrency // TODO [REDACTED_TASK_KEY] + private fun canIncludeFee(typeOfAmount: AmountType, feePaidCurrency: FeePaidCurrency): Boolean { + return when (feePaidCurrency) { + FeePaidCurrency.Coin -> typeOfAmount == AmountType.Coin + FeePaidCurrency.SameCurrency -> true + is FeePaidCurrency.Token -> { + val sendToken = (typeOfAmount as? AmountType.Token)?.token ?: return false - private fun isCoinAmount(typeOfAmount: AmountType): Boolean = typeOfAmount == AmountType.Coin + sendToken.contractAddress.equals(feePaidCurrency.token.contractAddress, ignoreCase = true) && + sendToken.name.equals(feePaidCurrency.token.name, ignoreCase = true) && + sendToken.symbol.equals(feePaidCurrency.token.symbol, ignoreCase = true) + } + } + } } internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState { diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt index 5a9808ea69..f23d760f1a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt @@ -2,7 +2,15 @@ package com.tangem.tap.features.send.redux.states // Shows only one type of the layout enum class ReceiptLayoutType { - UNKNOWN, FIAT, CRYPTO, TOKEN_FIAT, TOKEN_CRYPTO + UNKNOWN, + FIAT, + CRYPTO, + TOKEN_FIAT, + TOKEN_CRYPTO, + FEE_IN_CUSTOM_TOKEN, + FEE_IN_CUSTOM_TOKEN_FIAT, + SAME_CURRENCY, + SAME_CURRENCY_FIAT, } data class ReceiptState( @@ -11,6 +19,10 @@ data class ReceiptState( val crypto: ReceiptCrypto? = null, val tokenFiat: ReceiptTokenFiat? = null, val tokenCrypto: ReceiptTokenCrypto? = null, + val customTokenFiat: ReceiptTokenFiat? = null, + val customTokenCrypto: ReceiptTokenCrypto? = null, + val sameCurrencyFiat: ReceiptFiat? = null, + val sameCurrencyCrypto: ReceiptCrypto? = null, val mainCurrency: MainCurrency? = null, ) : SendScreenState { override val stateId: StateId = StateId.RECEIPT @@ -20,6 +32,7 @@ data class ReceiptSymbols( val fiat: String, val crypto: String, val token: String? = null, + val fee: String? = null, ) data class ReceiptFiat( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 589a563af2..1159e1ff0d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.isZero import com.tangem.core.navigation.StateDialog import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.CurrencyConverter import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.text.DecimalDigitsInputFilter @@ -31,8 +32,10 @@ interface SendScreenState : StateType, IdStateHolder data class SendState( val walletManager: WalletManager? = null, + val currency: CryptoCurrency? = null, val coinConverter: CurrencyConverter? = null, val tokenConverter: CurrencyConverter? = null, + val customFeeConverter: CurrencyConverter? = null, val lastChangedStates: LinkedHashSet = linkedSetOf(), val addressState: AddressState = AddressState(), val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), @@ -44,6 +47,7 @@ data class SendState( val dialog: StateDialog? = null, val externalTransactionData: ExternalTransactionData? = null, val isSuccessSend: Boolean = false, + val canIncludeFee: Boolean = false, ) : SendScreenState { override val stateId: StateId = StateId.SEND_SCREEN @@ -99,6 +103,9 @@ data class SendState( fun coinIsConvertible(): Boolean = coinConverter != null fun tokenIsConvertible(): Boolean = tokenConverter != null + fun currencyIsConvertible(): Boolean = coinConverter != null || tokenConverter != null + fun feeIsConvertible(): Boolean = customFeeConverter != null + fun mainCurrencyCanBeSwitched(): Boolean { return when (amountState.typeOfAmount) { AmountType.Coin -> coinIsConvertible() @@ -137,16 +144,13 @@ data class AmountState( val decimalSeparator: String = ".", val error: TapError? = null, val inputIsEnabled: Boolean = true, - private val feePaidInCurrencyNetworkCurrency: Boolean = false, ) : SendScreenState { override val stateId: StateId = StateId.AMOUNT fun isReady(): Boolean = error == null && !amountToSendCrypto.isZero() - private fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin - - fun canIncludeFee(): Boolean = isCoinAmount() || feePaidInCurrencyNetworkCurrency + fun canIncludeFee(): Boolean = store.state.sendState.canIncludeFee fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency { return if (!canSwitched) { diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 0802255fb8..927468b7ea 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -306,7 +306,7 @@ internal class SendStateSubscriber( } @Suppress("LongMethod", "ComplexMethod") - private fun handleReceiptState(fg: SendFragment, state: ReceiptState, feeProgressState: ProgressState) = + private fun handleReceiptState(fg: SendFragment, state: ReceiptState, feeProgressState: ProgressState) { with(fg.binding.clReceiptContainer) { val mainLayout = clReceiptContainer as ViewGroup val totalLayout = llTotalContainer.llTotal as ViewGroup @@ -410,9 +410,96 @@ internal class SendStateSubscriber( } llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString()) } - else -> {} + ReceiptLayoutType.FEE_IN_CUSTOM_TOKEN -> { + val receipt = state.customTokenCrypto ?: return + + totalLayout.show(false) + totalTokenLayout.show(true) + + tvReceiptAmountValue.update( + "${receipt.amountToken} ${receipt.symbols.token ?: receipt.symbols.crypto}", + ) + tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.fee}") + + val willSent = SpannableStringBuilder() + .bold { + append(roughOrEmpty(receipt.totalFiat)).append(" ") + append(receipt.symbols.fiat) + } + llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString()) + } + ReceiptLayoutType.FEE_IN_CUSTOM_TOKEN_FIAT -> { + val receipt = state.customTokenFiat ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}") + tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}") + llTotalContainer.tvTotalValue.post { + llTotalContainer.tvTotalValue.update( + "${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}", + ) + } + + val willSent = getString( + R.string.send_total_subtitle_asset_format, + "${receipt.symbols.token ?: receipt.symbols.crypto} ${receipt.willSentToken}", + "${receipt.symbols.fee} ${receipt.willSentFeeCoin}", + ) + llTotalContainer.tvWillBeSentValue.update(willSent) + } + + ReceiptLayoutType.SAME_CURRENCY -> { + val receipt = state.sameCurrencyCrypto ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + tvReceiptAmountValue.update( + "${receipt.amountCrypto} ${receipt.symbols.token ?: receipt.symbols.crypto}", + ) + tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.token ?: receipt.symbols.crypto}") + llTotalContainer.tvTotalValue.post { + llTotalContainer.tvTotalValue.update( + "${receipt.totalCrypto} ${receipt.symbols.token ?: receipt.symbols.crypto}", + ) + } + + if (receipt.willSentFiat == BigDecimalFormatter.EMPTY_BALANCE_SIGN) { + llTotalContainer.tvWillBeSentValue.hide() + } else { + llTotalContainer.tvWillBeSentValue.show() + llTotalContainer.tvWillBeSentValue.update( + getString( + R.string.send_total_subtitle_fiat_format, + "${receipt.willSentFiat} ${receipt.symbols.fiat}", + "${receipt.feeFiat} ${receipt.symbols.fiat}", + ), + ) + } + } + ReceiptLayoutType.SAME_CURRENCY_FIAT -> { + val receipt = state.sameCurrencyFiat ?: return + + totalLayout.show(true) + totalTokenLayout.show(false) + tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}") + tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}") + llTotalContainer.tvTotalValue.post { + llTotalContainer.tvTotalValue.update( + "${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}", + ) + } + + val willSent = getString( + R.string.send_total_subtitle_format, + "${receipt.willSentCrypto} ${receipt.symbols.token ?: receipt.symbols.crypto}", + ) + llTotalContainer.tvWillBeSentValue.update(willSent) + } + ReceiptLayoutType.UNKNOWN, null -> {} } } + } private companion object { const val ROUGH_SIGN = "≈" diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index dac4ffd59d..b1db79189b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -210,10 +210,14 @@ object TradeCryptoMiddleware { store.dispatchOnMain( action = PrepareSendScreen( walletManager = walletManager, + feePaidCurrency = walletManager.wallet.blockchain.feePaidCurrency(), + currency = currency, coinAmount = walletManager.wallet.amounts[AmountType.Coin], coinRate = action.coinFiatRate, tokenAmount = sendableAmount, tokenRate = action.tokenFiatRate, + feeCurrencyRate = action.feeCurrencyStatus?.value?.fiatRate, + feeCurrencyDecimals = action.feeCurrencyStatus?.currency?.decimals ?: 0, ), ) @@ -262,8 +266,12 @@ object TradeCryptoMiddleware { store.dispatchOnMain( action = PrepareSendScreen( walletManager = walletManager, + feePaidCurrency = walletManager.wallet.blockchain.feePaidCurrency(), + currency = currency, coinAmount = amountToSend, coinRate = cryptoStatus.value.fiatRate, + feeCurrencyRate = action.feeCurrencyStatus?.value?.fiatRate, + feeCurrencyDecimals = action.feeCurrencyStatus?.currency?.decimals ?: 0, ), ) } diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 3bb9f148e0..12777954f8 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -715,7 +715,7 @@ Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше Нравится Tangem? Необходима плата за аренду сети - %1$s - это токен в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести депозит в размере %4$s (%5$s), чтобы покрыть комиссию сети. + %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. Недостаточно %1$s для оплаты комиссии сети Отправка средств станет доступной после завершения транзакции %s Транзакция в обработке diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index c5ba957fac..585432c7dd 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -728,7 +728,7 @@ Your review keeps us motivated to make Tangem Wallet even better Enjoying Tangem? Network rent fee required - %1$s is a token in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. + %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. Insufficient %1$s to cover network fee Sending funds will be available once the %s transaction is complete Transaction pending diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index e25d693b6d..1fbc40be6a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -47,6 +47,7 @@ fun getActiveIconRes(blockchainId: String): Int { "NEAR", "NEAR/test" -> R.drawable.img_near_22 "decimal", "decimal/test" -> R.drawable.img_decimal_22 "xdc", "xdc/test" -> R.drawable.img_xdc_22 + "vechain", "vechain/test" -> R.drawable.img_vechain_22 else -> R.drawable.ic_alert_24 } } @@ -95,6 +96,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "near-protocol", "near-protocol/test" -> R.drawable.img_near_22 "decimal", "decimal/test" -> R.drawable.img_decimal_22 "xdc-network", "xdc-network/test" -> R.drawable.img_xdc_22 + "vechain", "vechain/test" -> R.drawable.img_vechain_22 else -> R.drawable.ic_alert_24 } } @@ -140,6 +142,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "near" -> R.drawable.img_near_22 "decimal" -> R.drawable.img_decimal_22 "xdce-crowd-sale" -> R.drawable.img_xdc_22 + "vechain" -> R.drawable.img_vechain_22 else -> R.drawable.ic_alert_24 } } @@ -188,6 +191,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "NEAR", "NEAR/test" -> R.drawable.ic_near_22 "decimal", "decimal/test" -> R.drawable.ic_decimal_22 "xdc", "xdc/test" -> R.drawable.ic_xdc_22 + "vechain", "vechain/test" -> R.drawable.ic_vechain_22 else -> R.drawable.ic_alert_24 } } @@ -236,6 +240,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "near-protocol", "near-protocol/test" -> R.drawable.ic_near_22 "decimal", "decimal/test" -> R.drawable.ic_decimal_22 "xdc-network", "xdc-network/test" -> R.drawable.ic_xdc_22 + "vechain", "vechain/test" -> R.drawable.ic_vechain_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_vechain_22.xml b/core/ui/src/main/res/drawable/ic_vechain_22.xml new file mode 100644 index 0000000000..513ac52357 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_vechain_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_vechain_22.xml b/core/ui/src/main/res/drawable/img_vechain_22.xml new file mode 100644 index 0000000000..ad8e27963e --- /dev/null +++ b/core/ui/src/main/res/drawable/img_vechain_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index ce6c4226b1..dba2fda4b9 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -30,6 +30,7 @@ internal object TokensDataModule { tangemExpressApi: TangemExpressApi, userTokensStore: UserTokensStore, userWalletsStore: UserWalletsStore, + walletManagersFacade: WalletManagersFacade, assetsStore: AssetsStore, cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, @@ -38,6 +39,7 @@ internal object TokensDataModule { tangemTechApi = tangemTechApi, tangemExpressApi = tangemExpressApi, userTokensStore = userTokensStore, + walletManagersFacade = walletManagersFacade, userWalletsStore = userWalletsStore, assetsStore = assetsStore, cacheRegistry = cacheRegistry, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index be19a43d03..c1bcd54351 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -23,8 +23,10 @@ import com.tangem.domain.core.error.DataError import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency @Suppress("LargeClass", "LongParameterList") internal class DefaultCurrenciesRepository( @@ -39,6 +42,7 @@ internal class DefaultCurrenciesRepository( private val tangemExpressApi: TangemExpressApi, private val userTokensStore: UserTokensStore, private val userWalletsStore: UserWalletsStore, + private val walletManagersFacade: WalletManagersFacade, private val assetsStore: AssetsStore, private val cacheRegistry: CacheRegistry, private val dispatchers: CoroutineDispatcherProvider, @@ -323,6 +327,32 @@ internal class DefaultCurrenciesRepository( } } + override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency { + val blockchain = Blockchain.fromId(currency.network.id.value) + return when (val feePaidCurrency = blockchain.feePaidCurrency()) { + FeePaidSdkCurrency.Coin -> FeePaidCurrency.Coin + FeePaidSdkCurrency.SameCurrency -> FeePaidCurrency.SameCurrency + is FeePaidSdkCurrency.Token -> { + val balance = walletManagersFacade.tokenBalance( + userWalletId = userWalletId, + network = currency.network, + name = feePaidCurrency.token.name, + symbol = feePaidCurrency.token.symbol, + contractAddress = feePaidCurrency.token.contractAddress, + decimals = feePaidCurrency.token.decimals, + id = feePaidCurrency.token.id, + ) + FeePaidCurrency.Token( + tokenId = getTokenId(network = currency.network, sdkToken = feePaidCurrency.token), + name = feePaidCurrency.token.name, + symbol = feePaidCurrency.token.symbol, + contractAddress = feePaidCurrency.token.contractAddress, + balance = balance, + ) + } + } + } + private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow> { return userTokensStore.get(userWallet.walletId).map { storedTokens -> responseCurrenciesFactory.createCurrencies( diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 4a08355267..1d03f1f1b3 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -76,8 +76,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "decimal/test" -> Blockchain.DecimalTestnet "xdc-network" -> Blockchain.XDC "xdc-network/test" -> Blockchain.XDCTestnet - "TODO" -> Blockchain.Vechain // TODO [REDACTED_TASK_KEY] - "TODO/test" -> Blockchain.VechainTestnet // TODO [REDACTED_TASK_KEY] + "vechain" -> Blockchain.Vechain + "vechain/test" -> Blockchain.VechainTestnet "aptos" -> Blockchain.Aptos "aptos/test" -> Blockchain.AptosTestnet else -> null @@ -157,8 +157,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.DecimalTestnet -> "decimal/test" Blockchain.XDC -> "xdc-network" Blockchain.XDCTestnet -> "xdc-network/test" - Blockchain.Vechain -> "TODO" // TODO [REDACTED_TASK_KEY] - Blockchain.VechainTestnet -> "TODO/test" // TODO [REDACTED_TASK_KEY] + Blockchain.Vechain -> "vechain" + Blockchain.VechainTestnet -> "vechain/test" Blockchain.Aptos -> "aptos" Blockchain.AptosTestnet -> "aptos/test" } @@ -209,8 +209,7 @@ fun Blockchain.toCoinId(): String { Blockchain.NearTestnet -> "near/test" Blockchain.Decimal, Blockchain.DecimalTestnet -> "decimal" Blockchain.XDC, Blockchain.XDCTestnet -> "xdce-crowd-sale" - Blockchain.Vechain -> "TODO" // TODO [REDACTED_TASK_KEY] - Blockchain.VechainTestnet -> "TODO/test" // TODO [REDACTED_TASK_KEY] + Blockchain.Vechain, Blockchain.VechainTestnet -> "vechain" Blockchain.Aptos -> "aptos" Blockchain.AptosTestnet -> "aptos/test" Blockchain.Unknown -> "unknown" @@ -240,6 +239,4 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5 private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Ducatus, - Blockchain.Vechain, // TODO [REDACTED_TASK_KEY] - Blockchain.VechainTestnet, // TODO [REDACTED_TASK_KEY] ) \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 87850c1161..45f2441c60 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -565,6 +565,35 @@ class DefaultWalletManagersFacade( return walletManager.wallet.recentTransactions.mapNotNull(transactionDataConverter::convert) } + override suspend fun tokenBalance( + userWalletId: UserWalletId, + network: Network, + name: String, + symbol: String, + contractAddress: String, + decimals: Int, + id: String?, + ): BigDecimal { + val blockchain = Blockchain.fromId(network.id.value) + val walletManager = getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = blockchain, + derivationPath = network.derivationPath.value, + ) + requireNotNull(walletManager) { "Unable to get a wallet manager for blockchain: $blockchain" } + return walletManager.wallet.fundsAvailable( + AmountType.Token( + token = Token( + name = name, + symbol = symbol, + contractAddress = contractAddress, + decimals = decimals, + id = id, + ), + ), + ) + } + private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) { if (tokens.isEmpty()) return diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 818989d9c9..4988a715c5 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -270,4 +270,15 @@ interface WalletManagersFacade { blockchain: Blockchain, derivationPath: String?, ): List + + @Suppress("LongParameterList") + suspend fun tokenBalance( + userWalletId: UserWalletId, + network: Network, + name: String, + symbol: String, + contractAddress: String, + decimals: Int, + id: String? = null, + ): BigDecimal } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/FeePaidCurrency.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/FeePaidCurrency.kt new file mode 100644 index 0000000000..50945df4f9 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/FeePaidCurrency.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.tokens.model + +import java.math.BigDecimal + +sealed class FeePaidCurrency { + object Coin : FeePaidCurrency() + object SameCurrency : FeePaidCurrency() + data class Token( + val tokenId: CryptoCurrency.ID, + val name: String, + val symbol: String, + val contractAddress: String, + val balance: BigDecimal, + ) : FeePaidCurrency() +} \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index d818e80eea..f0acf4eef2 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -15,6 +15,14 @@ sealed class CryptoCurrencyWarning { val coinCurrency: CryptoCurrency, ) : CryptoCurrencyWarning() + data class CustomTokenNotEnoughForFee( + val currency: CryptoCurrency, + val feeCurrency: CryptoCurrency?, + val networkName: String, + val feeCurrencyName: String, + val feeCurrencySymbol: String, + ) : CryptoCurrencyWarning() + object SomeNetworksUnreachable : CryptoCurrencyWarning() data class SomeNetworksNoAccount( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 2a0d6ad530..794cf3a5f9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -4,6 +4,7 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -11,10 +12,12 @@ import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* +import java.math.BigDecimal /** * Use case to determine which TokenActions are available for a [CryptoCurrency] @@ -104,7 +107,13 @@ class GetCryptoCurrencyActionsUseCase( activeList.add(TokenActionsState.ActionState.Receive(true)) // send - if (isSendDisabled(cryptoCurrencyStatus = cryptoCurrencyStatus, coinStatus = coinStatus)) { + if ( + isSendDisabled( + userWalletId = userWallet.walletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + coinStatus = coinStatus, + ) + ) { disabledList.add(TokenActionsState.ActionState.Send(false)) } else { activeList.add(TokenActionsState.ActionState.Send(true)) @@ -157,13 +166,41 @@ class GetCryptoCurrencyActionsUseCase( return activeList + disabledList } - private fun isSendDisabled( + private suspend fun isSendDisabled( + userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?, - ): Boolean = cryptoCurrencyStatus.value.amount.isNullOrZero() || - coinStatus?.value?.amount.isNullOrZero() || - currenciesRepository.hasPendingTransactions( - cryptoCurrencyStatus = cryptoCurrencyStatus, + ): Boolean { + val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, cryptoCurrencyStatus.currency) + val notEnoughBalanceForFee = isNotEnoughBalanceForFee( + feePaidCurrency = feePaidCurrency, + tokenStatus = cryptoCurrencyStatus, coinStatus = coinStatus, ) + return cryptoCurrencyStatus.value.amount.isNullOrZero() || + notEnoughBalanceForFee || + currenciesRepository.hasPendingTransactions( + cryptoCurrencyStatus = cryptoCurrencyStatus, + coinStatus = coinStatus, + ) + } + + private fun isNotEnoughBalanceForFee( + feePaidCurrency: FeePaidCurrency, + tokenStatus: CryptoCurrencyStatus, + coinStatus: CryptoCurrencyStatus?, + ): Boolean { + return when (feePaidCurrency) { + FeePaidCurrency.Coin -> !tokenStatus.value.amount.isZero() && coinStatus?.value?.amount.isZero() + FeePaidCurrency.SameCurrency -> tokenStatus.value.amount.isZero() + is FeePaidCurrency.Token -> { + val feePaidTokenBalance = feePaidCurrency.balance + !tokenStatus.value.amount.isZero() && feePaidTokenBalance.isZero() + } + } + } + + private fun BigDecimal?.isZero(): Boolean { + return this?.signum() == 0 + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 9fe2ba124a..9599d1a964 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.tokens import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations @@ -47,6 +48,7 @@ class GetCurrencyWarningsUseCase( ) return combine( getCoinRelatedWarnings( + userWalletId = userWalletId, operations = operations, networkId = currency.network.id, currencyId = currency.id, @@ -136,6 +138,7 @@ class GetCurrencyWarningsUseCase( @Suppress("LongParameterList") private suspend fun getCoinRelatedWarnings( + userWalletId: UserWalletId, operations: CurrenciesStatusesOperations, networkId: Network.ID, currencyId: CryptoCurrency.ID, @@ -162,14 +165,11 @@ class GetCurrencyWarningsUseCase( if (currenciesRepository.hasPendingTransactions(tokenStatus, coinStatus)) { add(CryptoCurrencyWarning.HasPendingTransactions(coinStatus.currency.symbol)) } - if (!tokenStatus.value.amount.isZero() && coinStatus.value.amount.isZero()) { - add( - CryptoCurrencyWarning.BalanceNotEnoughForFee( - tokenCurrency = tokenStatus.currency, - coinCurrency = coinStatus.currency, - ), - ) - } + getFeeWarning( + userWalletId = userWalletId, + coinStatus = coinStatus, + tokenStatus = tokenStatus, + )?.let(::add) } } else -> listOf(CryptoCurrencyWarning.SomeNetworksUnreachable) @@ -177,6 +177,72 @@ class GetCurrencyWarningsUseCase( } } + private suspend fun getFeeWarning( + userWalletId: UserWalletId, + coinStatus: CryptoCurrencyStatus, + tokenStatus: CryptoCurrencyStatus, + ): CryptoCurrencyWarning? { + val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency) + return when { + feePaidCurrency is FeePaidCurrency.Coin && + !tokenStatus.value.amount.isZero() && + coinStatus.value.amount.isZero() -> { + CryptoCurrencyWarning.BalanceNotEnoughForFee( + tokenCurrency = tokenStatus.currency, + coinCurrency = coinStatus.currency, + ) + } + feePaidCurrency is FeePaidCurrency.SameCurrency && !tokenStatus.value.amount.isZero() -> { + CryptoCurrencyWarning.BalanceNotEnoughForFee( + tokenCurrency = tokenStatus.currency, + coinCurrency = coinStatus.currency, + ) + } + feePaidCurrency is FeePaidCurrency.Token -> { + val feePaidTokenBalance = feePaidCurrency.balance + if (!tokenStatus.value.amount.isZero() && feePaidTokenBalance.isZero()) { + constructTokenBalanceNotEnoughWarning( + userWalletId = userWalletId, + tokenStatus = tokenStatus, + feePaidToken = feePaidCurrency, + ) + } else { + null + } + } + else -> null + } + } + + private suspend fun constructTokenBalanceNotEnoughWarning( + userWalletId: UserWalletId, + tokenStatus: CryptoCurrencyStatus, + feePaidToken: FeePaidCurrency.Token, + ): CryptoCurrencyWarning { + val token = currenciesRepository + .getMultiCurrencyWalletCurrenciesSync(userWalletId) + .find { + it is CryptoCurrency.Token && it.contractAddress.equals(feePaidToken.contractAddress, ignoreCase = true) + } + return if (token != null) { + CryptoCurrencyWarning.CustomTokenNotEnoughForFee( + currency = tokenStatus.currency, + feeCurrency = token, + networkName = token.network.name, + feeCurrencyName = feePaidToken.name, + feeCurrencySymbol = feePaidToken.symbol, + ) + } else { + CryptoCurrencyWarning.CustomTokenNotEnoughForFee( + currency = tokenStatus.currency, + feeCurrency = null, + networkName = tokenStatus.currency.network.name, + feeCurrencyName = feePaidToken.name, + feeCurrencySymbol = feePaidToken.symbol, + ) + } + } + private fun getNetworkUnavailableWarning(currencyStatus: CryptoCurrencyStatus): CryptoCurrencyWarning? { return (currencyStatus.value as? CryptoCurrencyStatus.Unreachable)?.let { CryptoCurrencyWarning.SomeNetworksUnreachable diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt new file mode 100644 index 0000000000..36e147306c --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt @@ -0,0 +1,51 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency +import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +class GetFeePaidCryptoCurrencyStatusSyncUseCase( + internal val currenciesRepository: CurrenciesRepository, + internal val quotesRepository: QuotesRepository, + internal val networksRepository: NetworksRepository, + internal val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): Either { + val cryptoCurrency = cryptoCurrencyStatus.currency + val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, cryptoCurrency) + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return either { + when (feePaidCurrency) { + FeePaidCurrency.Coin -> getCryptoCurrency(operations, cryptoCurrency.id) + FeePaidCurrency.SameCurrency -> cryptoCurrencyStatus + is FeePaidCurrency.Token -> getCryptoCurrency(operations, feePaidCurrency.tokenId) + } + } + } + + private suspend fun getCryptoCurrency( + operations: CurrenciesStatusesOperations, + cryptoCurrencyId: CryptoCurrency.ID, + ): CryptoCurrencyStatus? { + return operations.getCurrencyStatusSync(cryptoCurrencyId).getOrNull() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt index 09eaa23946..85abbb2bc8 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt @@ -36,9 +36,14 @@ sealed class TradeCryptoAction : Action { val tokenCurrency: CryptoCurrency.Token, val tokenFiatRate: BigDecimal?, val coinFiatRate: BigDecimal?, + val feeCurrencyStatus: CryptoCurrencyStatus?, ) : New() - data class SendCoin(val userWallet: UserWallet, val coinStatus: CryptoCurrencyStatus) : New() + data class SendCoin( + val userWallet: UserWallet, + val coinStatus: CryptoCurrencyStatus, + val feeCurrencyStatus: CryptoCurrencyStatus?, + ) : New() data class Swap(val cryptoCurrency: CryptoCurrency) : New() } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 3901e5ad7a..60f8d5da7e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -2,6 +2,7 @@ package com.tangem.domain.tokens.repository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -175,4 +176,9 @@ interface CurrenciesRepository { * @param coinStatus main currency status in [cryptoCurrencyStatus] network */ fun hasPendingTransactions(cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?): Boolean + + /** + * Retrieves fee paid currency for specific [currency]. + */ + suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index ffdd5c834d..6fb77065d5 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -115,4 +116,8 @@ internal class MockCurrenciesRepository( ): Boolean { return false } + + override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency { + return FeePaidCurrency.Coin + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 451a51f469..0965959789 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -647,7 +647,10 @@ internal class SwapInteractorImpl @Inject constructor( gasPrice = (feeAmountWithDecimals / fee.gasLimit.toBigDecimal()).toBigInteger(), ) } - Blockchain.Vechain -> TODO("[REDACTED_TASK_KEY]") + Blockchain.Vechain -> Fee.Vechain( + amount = feeAmount, + gasPriceCoef = fee.gasLimit, + ) Blockchain.Aptos -> { Fee.Aptos( amount = feeAmount, @@ -1342,7 +1345,7 @@ internal class SwapInteractorImpl @Inject constructor( return when (this) { is Fee.Common -> 0 is Fee.Ethereum -> gasLimit.toInt() - is Fee.Vechain -> TODO("[REDACTED_TASK_KEY]") + is Fee.Vechain -> gasPriceCoef is Fee.Aptos -> amount.longValue?.div(gasUnitPrice)?.toInt() ?: 0 } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 78a69221f1..9c3c74dfd6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -30,7 +30,9 @@ internal class TokenDetailsNotificationsAnalyticsSender( private fun getEvent(notification: TokenDetailsNotification): AnalyticsEvent? { return when (notification) { - is TokenDetailsNotification.NetworkFee -> TokenDetailsAnalyticsEvent.Notice.NotEnoughFee( + is TokenDetailsNotification.NetworkFee, + is TokenDetailsNotification.NetworkFeeWithBuyButton, + -> TokenDetailsAnalyticsEvent.Notice.NotEnoughFee( currency = cryptoCurrency, ) is TokenDetailsNotification.NetworksUnreachable, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index dbe63a69f8..e880e6aa3e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.features.tokendetails.impl.R @@ -62,28 +63,55 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ) data class NetworkFee( - private val feeInfo: CryptoCurrencyWarning.BalanceNotEnoughForFee, - private val onBuyClick: () -> Unit, + private val currency: CryptoCurrency, + private val networkName: String, + private val feeCurrencyName: String, + private val feeCurrencySymbol: String, ) : Warning( title = TextReference.Res( id = R.string.warning_send_blocked_funds_for_fee_title, - formatArgs = wrappedList(feeInfo.coinCurrency.name), + formatArgs = wrappedList(feeCurrencyName), ), subtitle = TextReference.Res( id = R.string.warning_send_blocked_funds_for_fee_message, formatArgs = wrappedList( - feeInfo.tokenCurrency.name, - feeInfo.coinCurrency.name, - feeInfo.tokenCurrency.name, - feeInfo.coinCurrency.name, - feeInfo.coinCurrency.symbol, + currency.name, + networkName, + currency.name, + feeCurrencyName, + feeCurrencySymbol, ), ), - iconResId = feeInfo.tokenCurrency.networkIconResId, - buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + iconResId = currency.networkIconResId, + ) + + data class NetworkFeeWithBuyButton( + private val currency: CryptoCurrency, + private val networkName: String, + private val feeCurrencyName: String, + private val feeCurrencySymbol: String, + private val onBuyClick: () -> Unit, + ) : Warning( + title = TextReference.Res( + id = R.string.warning_send_blocked_funds_for_fee_title, + formatArgs = wrappedList(feeCurrencyName), + ), + subtitle = TextReference.Res( + id = R.string.warning_send_blocked_funds_for_fee_message, + formatArgs = wrappedList( + currency.name, + networkName, + currency.name, + feeCurrencyName, + feeCurrencySymbol, + ), + ), + iconResId = currency.networkIconResId, + buttonsState = + NotificationConfig.ButtonsState.SecondaryButtonConfig( text = resourceReference( id = R.string.common_buy_currency, - formatArgs = wrappedList(feeInfo.coinCurrency.symbol), + formatArgs = wrappedList(feeCurrencySymbol), ), onClick = onBuyClick, ), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index de2e921017..caad6173cc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -25,10 +25,32 @@ internal class TokenDetailsNotificationConverter( private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification { return when (warning) { - is CryptoCurrencyWarning.BalanceNotEnoughForFee -> TokenDetailsNotification.NetworkFee( - feeInfo = warning, + is CryptoCurrencyWarning.BalanceNotEnoughForFee -> TokenDetailsNotification.NetworkFeeWithBuyButton( + currency = warning.tokenCurrency, + networkName = warning.coinCurrency.name, + feeCurrencyName = warning.coinCurrency.name, + feeCurrencySymbol = warning.coinCurrency.symbol, onBuyClick = { clickIntents.onBuyCoinClick(warning.coinCurrency) }, ) + is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> { + val feeCurrency = warning.feeCurrency + if (feeCurrency != null) { + TokenDetailsNotification.NetworkFeeWithBuyButton( + currency = warning.currency, + networkName = feeCurrency.network.name, + feeCurrencyName = warning.feeCurrencyName, + feeCurrencySymbol = warning.feeCurrencySymbol, + onBuyClick = { clickIntents.onBuyCoinClick(feeCurrency) }, + ) + } else { + TokenDetailsNotification.NetworkFee( + currency = warning.currency, + networkName = warning.networkName, + feeCurrencyName = warning.feeCurrencyName, + feeCurrencySymbol = warning.feeCurrencySymbol, + ) + } + } is CryptoCurrencyWarning.ExistentialDeposit -> TokenDetailsNotification.ExistentialDeposit( existentialInfo = warning, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index cf03f55529..7a7d53ba09 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -74,6 +74,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, @@ -356,23 +357,35 @@ internal class TokenDetailsViewModel @Inject constructor( val cryptoCurrencyStatus = cryptoCurrencyStatus ?: return viewModelScope.launch(dispatchers.main) { + val maybeFeeCurrencyStatus = + getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, cryptoCurrencyStatus).getOrNull() + when (val currency = cryptoCurrencyStatus.currency) { is CryptoCurrency.Coin -> { reduxStateHolder.dispatch( action = TradeCryptoAction.New.SendCoin( userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch }, coinStatus = cryptoCurrencyStatus, + feeCurrencyStatus = maybeFeeCurrencyStatus, ), ) } is CryptoCurrency.Token -> { - sendToken(tokenCurrency = currency, tokenFiatRate = cryptoCurrencyStatus.value.fiatRate) + sendToken( + tokenCurrency = currency, + tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, + feeCurrencyStatus = maybeFeeCurrencyStatus, + ) } } } } - private fun sendToken(tokenCurrency: CryptoCurrency.Token, tokenFiatRate: BigDecimal?) { + private fun sendToken( + tokenCurrency: CryptoCurrency.Token, + tokenFiatRate: BigDecimal?, + feeCurrencyStatus: CryptoCurrencyStatus?, + ) { viewModelScope.launch(dispatchers.io) { val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch } val maybeCoinStatus = getNetworkCoinStatusUseCase( @@ -394,6 +407,7 @@ internal class TokenDetailsViewModel @Inject constructor( ifLeft = { null }, ifRight = { it.value.fiatRate }, ), + feeCurrencyStatus = feeCurrencyStatus, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index e2b21cc95f..065a47b196 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -110,6 +110,7 @@ internal class WalletViewModel @Inject constructor( private val unlockWalletsUseCase: UnlockWalletsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase, @@ -636,9 +637,17 @@ internal class WalletViewModel @Inject constructor( event = TokenScreenAnalyticsEvent.ButtonSend(coinStatus.currency.symbol), ) - reduxStateHolder.dispatch( - action = TradeCryptoAction.New.SendCoin(userWallet = userWallet, coinStatus = coinStatus), - ) + viewModelScope.launch(dispatchers.main) { + val maybeFeeCurrencyStatus = + getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, coinStatus).getOrNull() + reduxStateHolder.dispatch( + action = TradeCryptoAction.New.SendCoin( + userWallet = userWallet, + coinStatus = coinStatus, + feeCurrencyStatus = maybeFeeCurrencyStatus, + ), + ) + } } override fun onMultiCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { @@ -649,21 +658,30 @@ internal class WalletViewModel @Inject constructor( ) val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - when (cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> { - uiState = stateFactory.getStateWithClosedBottomSheet() - reduxStateHolder.dispatch( - action = TradeCryptoAction.New.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - ), - ) + viewModelScope.launch(dispatchers.main) { + val maybeFeeCurrencyStatus = + getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, cryptoCurrencyStatus).getOrNull() + when (cryptoCurrencyStatus.currency) { + is CryptoCurrency.Coin -> { + uiState = stateFactory.getStateWithClosedBottomSheet() + reduxStateHolder.dispatch( + action = TradeCryptoAction.New.SendCoin( + userWallet = userWallet, + coinStatus = cryptoCurrencyStatus, + feeCurrencyStatus = maybeFeeCurrencyStatus, + ), + ) + } + is CryptoCurrency.Token -> sendToken(userWallet, cryptoCurrencyStatus, maybeFeeCurrencyStatus) } - is CryptoCurrency.Token -> sendToken(userWallet, cryptoCurrencyStatus) } } - private fun sendToken(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) { + private fun sendToken( + userWallet: UserWallet, + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeCurrencyStatus: CryptoCurrencyStatus?, + ) { viewModelScope.launch(dispatchers.io) { getNetworkCoinStatusUseCase( userWalletId = userWallet.walletId, @@ -681,6 +699,7 @@ internal class WalletViewModel @Inject constructor( tokenCurrency = requireNotNull(cryptoCurrencyStatus.currency as? CryptoCurrency.Token), tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, coinFiatRate = coinStatus.value.fiatRate, + feeCurrencyStatus = feeCurrencyStatus, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 995e0ab13e..3fd26c242b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -13,10 +13,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase -import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase -import com.tangem.domain.tokens.RemoveCurrencyUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -76,6 +73,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getExploreUrlUseCase: GetExploreUrlUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -91,22 +89,39 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) - - when (val currency = cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> sendCoin(cryptoCurrencyStatus, userWallet) - is CryptoCurrency.Token -> sendToken(currency, cryptoCurrencyStatus.value, userWallet) + viewModelScope.launch(dispatchers.main) { + val maybeFeeCurrencyStatus = + getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, cryptoCurrencyStatus).getOrNull() + when (val currency = cryptoCurrencyStatus.currency) { + is CryptoCurrency.Coin -> sendCoin(cryptoCurrencyStatus, userWallet, maybeFeeCurrencyStatus) + is CryptoCurrency.Token -> sendToken( + cryptoCurrency = currency, + cryptoCurrencyStatus = cryptoCurrencyStatus.value, + feeCurrencyStatus = maybeFeeCurrencyStatus, + userWallet = userWallet, + ) + } } } - private fun sendCoin(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet) { + private fun sendCoin( + cryptoCurrencyStatus: CryptoCurrencyStatus, + userWallet: UserWallet, + feeCurrencyStatus: CryptoCurrencyStatus?, + ) { reduxStateHolder.dispatch( - action = TradeCryptoAction.New.SendCoin(userWallet = userWallet, coinStatus = cryptoCurrencyStatus), + action = TradeCryptoAction.New.SendCoin( + userWallet = userWallet, + coinStatus = cryptoCurrencyStatus, + feeCurrencyStatus = feeCurrencyStatus, + ), ) } private fun sendToken( cryptoCurrency: CryptoCurrency.Token, cryptoCurrencyStatus: CryptoCurrencyStatus.Status, + feeCurrencyStatus: CryptoCurrencyStatus?, userWallet: UserWallet, ) { viewModelScope.launch(dispatchers.main) { @@ -125,6 +140,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( tokenCurrency = cryptoCurrency, tokenFiatRate = cryptoCurrencyStatus.fiatRate, coinFiatRate = coinStatus.value.fiatRate, + feeCurrencyStatus = feeCurrencyStatus, ), ) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index fb1268f9f8..2fcde13f2c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ spr-client = "3.6.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-453" +tangemBlockchainSdk = "develop-458" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-318" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^