Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-15 19:39:26 +03:00
commit 4d97201cb5
11 changed files with 150 additions and 101 deletions

View file

@ -13,6 +13,7 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.hexToBigDecimal
@ -85,7 +86,7 @@ class WalletConnectSdkHelper {
val transactionData = TransactionData(
amount = Amount(value, wallet.blockchain),
fee = Amount(fee, wallet.blockchain),
fee = Fee.Common(Amount(fee, wallet.blockchain)),
sourceAddress = transaction.from,
destinationAddress = transaction.to!!,
extras = EthereumTransactionExtras(
@ -194,8 +195,7 @@ class WalletConnectSdkHelper {
val dataToSign = EthereumUtils.buildTransactionToSign(
transactionData = data.transaction,
nonce = null,
blockchain = data.walletManager.wallet.blockchain,
gasLimit = null,
blockchain = data.walletManager.wallet.blockchain
) ?: return null
val command = SignHashCommand(

View file

@ -6,6 +6,8 @@ import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
@ -84,14 +86,14 @@ object DemoHelper {
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
override suspend fun getFee(amount: Amount, destination: String): Result<TransactionFee> {
val blockchain = walletManager.wallet.blockchain
return Result.Success(
listOf(
Amount(0.0001.toBigDecimal(), blockchain),
Amount(0.0002.toBigDecimal(), blockchain),
Amount(0.0003.toBigDecimal(), blockchain),
),
TransactionFee.Choosable(
minimum = Fee.Common(Amount(minimumFee, blockchain)),
normal = Fee.Common(Amount(normalFee, blockchain)),
priority = Fee.Common(Amount(priorityFee, blockchain))
)
)
}
@ -120,5 +122,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact
companion object {
val ID = DemoTransactionSender::class.java.simpleName
private val minimumFee = 0.0001.toBigDecimal()
private val normalFee = 0.0002.toBigDecimal()
private val priorityFee = 0.0003.toBigDecimal()
}
}

View file

@ -9,6 +9,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.successOr
@ -138,7 +140,7 @@ class GnosisRegistrator(
val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign(
transactionData = walletManager.createTransaction(
amount = approveAmount,
fee = feeAmount,
fee = Fee.Common(feeAmount),
destination = otpProcessorContractAddress,
),
nonce = atomicNonce.getAndIncrement().toBigInteger(),
@ -189,7 +191,7 @@ class GnosisRegistrator(
val transactionData = walletManager.createTransferFromTransaction(
amount = amount,
fee = hardcodeFeeAmount,
feeAmount = hardcodeFeeAmount,
source = addressTreasureSafe,
)
val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign(
@ -205,13 +207,17 @@ class GnosisRegistrator(
}
@Suppress("MagicNumber")
private fun Result<List<Amount>>.extractFeeAmount(): Result<Amount> {
private fun Result<TransactionFee>.extractFeeAmount(): Result<Amount> {
return when (this) {
is Result.Success -> {
if (this.data.size != 3) {
Result.Failure(BlockchainSdkError.FailedToLoadFee)
} else {
Result.Success(this.data[1])
when(data) {
is TransactionFee.Single -> {
Result.Failure(BlockchainSdkError.FailedToLoadFee)
}
is TransactionFee.Choosable -> {
val normalFee = (data as TransactionFee.Choosable).normal
Result.Success(normalFee.amount)
}
}
}
is Result.Failure -> this

View file

@ -4,6 +4,7 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.StateDialog
@ -127,7 +128,7 @@ sealed class FeeAction : SendScreenAction {
object RequestFee : FeeAction()
sealed class FeeCalculation : FeeAction() {
data class SetFeeResult(val fee: List<Amount>) : FeeCalculation()
data class SetFeeResult(val fee: TransactionFee) : FeeCalculation()
object ClearResult : FeeCalculation()
}

View file

@ -67,7 +67,7 @@ class AmountMiddleware {
}
val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend(inputCrypto))
val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee)
val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee?.amount)
val amountFieldErrors = filterErrorsForAmountField(transactionErrors)
if (amountFieldErrors.isEmpty()) {
dispatch(AmountAction.SetAmountError(null))

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
@ -53,15 +54,18 @@ class RequestFeeMiddleware {
val result = feeResult.data
// val result = FeeMock.getFee(walletManager.wallet.blockchain)
dispatch(FeeAction.FeeCalculation.SetFeeResult(result))
if (result.size == 1) {
val fee = result[0].value ?: BigDecimal.ZERO
if (fee.isZero()) {
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false))
when(result) {
is TransactionFee.Single -> {
val fee = result.normal.amount.value ?: BigDecimal.ZERO
if (fee.isZero()) {
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false))
}
}
is TransactionFee.Choosable -> {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true))
}
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true))
}
}
is Result.Failure -> {

View file

@ -12,6 +12,7 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionError
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
@ -118,11 +119,11 @@ private fun verifyAndSendTransaction(
val card = appState.globalState.scanResponse?.card ?: return
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
val typedAmount = sendState.amountState.amountToExtract ?: return
val feeAmount = sendState.feeState.currentFee ?: return
val currentFee = sendState.feeState.currentFee ?: return
val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend())
val transactionErrors = walletManager.validateTransaction(amountToSend, feeAmount)
val transactionErrors = walletManager.validateTransaction(amountToSend, currentFee.amount)
when {
transactionErrors.contains(TransactionError.TezosSendAll) -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
@ -134,7 +135,7 @@ private fun verifyAndSendTransaction(
},
sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
action, walletManager, amountToSend, currentFee, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch,
)
@ -145,7 +146,7 @@ private fun verifyAndSendTransaction(
}
else -> {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
action, walletManager, amountToSend, currentFee, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch,
)
}
@ -157,7 +158,7 @@ private fun sendTransaction(
action: SendActionUi.SendAmountToRecipient,
walletManager: WalletManager,
amountToSend: Amount,
feeAmount: Amount,
fee: Fee,
destinationAddress: String,
transactionExtras: TransactionExtrasState,
card: CardDTO,
@ -165,7 +166,7 @@ private fun sendTransaction(
dispatch: (Action) -> Unit,
) {
dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS))
var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress)
var txData = walletManager.createTransaction(amountToSend, fee, destinationAddress)
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
@ -185,7 +186,7 @@ private fun sendTransaction(
updateFeedbackManagerInfo(
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
feeAmount = fee.amount,
destinationAddress = destinationAddress,
)
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error = error))
@ -266,7 +267,7 @@ private fun sendTransaction(
updateFeedbackManagerInfo(
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
feeAmount = fee.amount,
destinationAddress = destinationAddress,
)
val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext

View file

@ -1,18 +1,22 @@
package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.FeeActionUi
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.FeeState
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.wallet.redux.ProgressState
/**
[REDACTED_AUTHOR]
*/
class FeeReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) {
is FeeActionUi -> handleUiAction(action, sendState, sendState.feeState)
is FeeAction -> handleAction(action, sendState, sendState.feeState)
@ -25,10 +29,12 @@ class FeeReducer : SendInternalReducer {
state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible)
}
is FeeActionUi.ChangeSelectedFee -> {
val currentFee = createValueOfFeeAmount(action.feeType, state.feeList)
val currentFee = state.fees?.let {
createValueOfFeeAmount(action.feeType, it)
}
state.copy(
selectedFeeType = action.feeType,
currentFee = currentFee,
currentFee = currentFee
)
}
is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded)
@ -37,6 +43,7 @@ class FeeReducer : SendInternalReducer {
}
private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState {
val result = when (action) {
is FeeAction.RequestFee -> {
state.copy(progressState = ProgressState.Loading)
@ -50,34 +57,36 @@ class FeeReducer : SendInternalReducer {
)
}
is FeeAction.FeeCalculation.SetFeeResult -> {
val fees = action.fee
if (fees.size == 1) {
val feeType = FeeType.SINGLE
val currentFee = createValueOfFeeAmount(feeType, fees)
when (val fees = action.fee) {
is TransactionFee.Single -> {
val feeType = FeeType.SINGLE
val currentFee = createValueOfFeeAmount(feeType, fees)
state.copy(
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
} else {
val feeType = getCurrentFeeType(state)
val currentFee = createValueOfFeeAmount(feeType, fees)
state.copy(
selectedFeeType = feeType,
fees = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
}
is TransactionFee.Choosable -> {
val feeType = getCurrentFeeType(state)
val currentFee = createValueOfFeeAmount(feeType, fees)
state.copy(
selectedFeeType = feeType,
feeList = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
state.copy(
selectedFeeType = feeType,
fees = fees,
currentFee = currentFee,
feeIsApproximate = isFeeApproximate(sendState),
)
}
}.copy(
progressState = ProgressState.Done,
)
}
FeeAction.FeeCalculation.ClearResult -> {
state.copy(
feeList = null,
fees = null,
currentFee = null,
progressState = ProgressState.Done,
)
@ -87,17 +96,18 @@ class FeeReducer : SendInternalReducer {
return updateLastState(sendState.copy(feeState = result), result)
}
private fun createValueOfFeeAmount(feeType: FeeType, list: List<Amount>?): Amount? {
if (list == null || list.isEmpty()) return null
return if (list.size == 1) {
list[0]
} else {
when (feeType) {
FeeType.SINGLE -> list[1]
FeeType.LOW -> list[0]
FeeType.NORMAL -> list[1]
FeeType.PRIORITY -> list[2]
private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Fee {
return when (transactionFee) {
is TransactionFee.Single -> {
transactionFee.normal
}
is TransactionFee.Choosable -> {
when (feeType) {
FeeType.SINGLE -> transactionFee.normal
FeeType.LOW -> transactionFee.minimum
FeeType.NORMAL -> transactionFee.normal
FeeType.PRIORITY -> transactionFee.priority
}
}
}
}
@ -112,4 +122,6 @@ class FeeReducer : SendInternalReducer {
private fun getCurrentFeeType(state: FeeState): FeeType {
return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.tap.features.send.redux.states
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.tap.features.wallet.redux.ProgressState
import java.math.BigDecimal
@ -13,8 +15,8 @@ enum class FeeType {
data class FeeState(
val selectedFeeType: FeeType = FeeType.NORMAL,
val feeList: List<Amount>? = null,
val currentFee: Amount? = null,
val fees: TransactionFee? = null,
val currentFee: Fee? = null,
val feeIsIncluded: Boolean = false,
val feeIsApproximate: Boolean = false,
val mainLayoutIsVisible: Boolean = false,
@ -28,5 +30,5 @@ data class FeeState(
fun isReady(): Boolean = currentFee != null
fun getCurrentFeeValue(): BigDecimal = currentFee?.value ?: BigDecimal.ZERO
fun getCurrentFeeValue(): BigDecimal = currentFee?.amount?.value ?: BigDecimal.ZERO
}

View file

@ -72,7 +72,7 @@ class CurrencyExchangeManager(
return when (action) {
Action.Buy -> buyService
Action.Sell -> sellService
} as ExchangeUrlBuilder
}
}
enum class Action { Buy, Sell }
@ -96,15 +96,14 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
val amountToSend = Amount(walletManager.wallet.blockchain)
val destinationAddress = token.contractAddress
val feeResult =
walletManager.getFee(
val feeResult = walletManager.getFee(
amountToSend,
destinationAddress,
) as? Result.Success ?: return
val fee = feeResult.data[0]
val fee = feeResult.data.minimum
val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
if (coinValue < fee.value) return
if (coinValue < fee.amount.value) return
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)

View file

@ -6,6 +6,8 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.isNetworkError
@ -98,7 +100,7 @@ class TransactionManagerImpl(
): SendTxResult {
val txData = walletManager.createTransaction(
amount = amount,
fee = Amount(value = feeAmount, blockchain = blockchain),
fee = Fee.Common(Amount(value = feeAmount, blockchain = blockchain)),
destination = destinationAddress,
).copy(hash = dataToSign, extras = createExtras(walletManager, gasLimit, dataToSign))
@ -197,24 +199,38 @@ class TransactionManagerImpl(
return when (fee) {
is Result.Success -> {
// for not EVM blockchains set gasLimit ZERO for now
val firstFee = fee.data.firstOrNull() ?: error("no fee found")
val minFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = firstFee),
)
val normalFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(fee.data.getOrNull(index = 1) ?: firstFee),
)
val priorityFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(fee.data.getOrNull(index = 2) ?: firstFee),
)
ProxyFees(
minFee = minFee,
normalFee = normalFee,
priorityFee = priorityFee,
)
when (fee.data) {
is TransactionFee.Single -> {
val fee = (fee.data as TransactionFee.Single).normal
val singleFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = fee.amount),
)
ProxyFees(
minFee = singleFee,
normalFee = singleFee,
priorityFee = singleFee
)
}
is TransactionFee.Choosable -> {
val choosableFee = fee.data as TransactionFee.Choosable
ProxyFees(
minFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
),
normalFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
),
priorityFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
)
)
}
}
}
is Result.Failure -> {
error(fee.error.message ?: fee.error.customMessage)
@ -263,19 +279,21 @@ class TransactionManagerImpl(
}
return when (fee) {
is Result.Success -> {
val minFee = fee.data.firstOrNull() ?: error("no fee found")
val choosableFee = fee.data
val minProxyFee = ProxyFee(
gasLimit = walletManager.gasLimit ?: BigInteger.ZERO,
fee = convertToProxyAmount(minFee),
gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
)
val normalProxyFee = ProxyFee(
gasLimit = walletManager.gasLimit ?: BigInteger.ZERO,
fee = convertToProxyAmount(fee.data.getOrNull(index = 1) ?: minFee),
gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
)
val priorityProxyFee = ProxyFee(
gasLimit = walletManager.gasLimit ?: BigInteger.ZERO,
fee = convertToProxyAmount(fee.data.lastOrNull() ?: minFee),
gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
)
ProxyFees(
minFee = minProxyFee,
normalFee = normalProxyFee,