Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-15 18:07:42 +08:00
parent 7e79305db7
commit 55942f2208
13 changed files with 281 additions and 71 deletions

View file

@ -224,26 +224,33 @@ class TransactionManagerImpl(
when (fee.data) {
is TransactionFee.Single -> {
val normalFee = (fee.data as TransactionFee.Single).normal
val singleFee = ProxyFee(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = normalFee.amount),
)
ProxyFees.SingleFee(
singleFee = singleFee,
)
val singleFee = if (normalFee as? Fee.CardanoToken != null) {
ProxyFee.CardanoToken(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = normalFee.amount),
minAdaValue = normalFee.minAdaValue,
)
} else {
ProxyFee.Common(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = normalFee.amount),
)
}
ProxyFees.SingleFee(singleFee = singleFee)
}
is TransactionFee.Choosable -> {
val choosableFee = fee.data as TransactionFee.Choosable
ProxyFees.MultipleFees(
minFee = ProxyFee(
minFee = ProxyFee.Common(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
),
normalFee = ProxyFee(
normalFee = ProxyFee.Common(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
),
priorityFee = ProxyFee(
priorityFee = ProxyFee.Common(
gasLimit = BigInteger.ZERO,
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
),
@ -300,15 +307,15 @@ class TransactionManagerImpl(
is Result.Success -> {
val choosableFee = fee.data
val minProxyFee = ProxyFee(
val minProxyFee = ProxyFee.Common(
gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
)
val normalProxyFee = ProxyFee(
val normalProxyFee = ProxyFee.Common(
gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
)
val priorityProxyFee = ProxyFee(
val priorityProxyFee = ProxyFee.Common(
gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit,
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
)
@ -463,7 +470,7 @@ class TransactionManagerImpl(
scale = blockchain.decimals(),
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
)
val minFee = ProxyFee(
val minFee = ProxyFee.Common(
gasLimit = gasLimit,
fee = ProxyAmount(
currencySymbol = blockchain.currency,
@ -471,7 +478,7 @@ class TransactionManagerImpl(
decimals = blockchain.decimals(),
),
)
val normalFee = ProxyFee(
val normalFee = ProxyFee.Common(
gasLimit = gasLimit,
fee = ProxyAmount(
currencySymbol = blockchain.currency,
@ -479,7 +486,7 @@ class TransactionManagerImpl(
decimals = blockchain.decimals(),
),
)
val priorityFee = ProxyFee(
val priorityFee = ProxyFee.Common(
gasLimit = gasLimit,
fee = ProxyAmount(
currencySymbol = blockchain.currency,

View file

@ -57,7 +57,7 @@ internal class DefaultTransactionRepository(
override suspend fun validateTransaction(
amount: Amount,
fee: Fee,
fee: Fee?,
memo: String?,
destination: String,
userWalletId: UserWalletId,
@ -65,9 +65,10 @@ internal class DefaultTransactionRepository(
isSwap: Boolean,
hash: String?,
): Result<Unit> {
val blockchain = Blockchain.fromId(network.id.value)
val walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = Blockchain.fromId(network.id.value),
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
@ -76,7 +77,7 @@ internal class DefaultTransactionRepository(
return if (validator != null) {
val transaction = walletManager.createTransactionInternal(
amount = amount,
fee = fee,
fee = fee ?: Fee.Common(amount = amount),
memo = memo,
destination = destination,
network = network,

View file

@ -25,13 +25,13 @@ interface TransactionRepository {
@Suppress("LongParameterList")
suspend fun validateTransaction(
amount: Amount,
fee: Fee,
fee: Fee?,
memo: String?,
destination: String,
userWalletId: UserWalletId,
network: Network,
isSwap: Boolean,
hash: String?,
isSwap: Boolean = false,
hash: String? = null,
): Result<Unit>
suspend fun sendTransaction(

View file

@ -435,8 +435,6 @@ internal class SendNotificationFactory(
addCardanoTransactionValidationError(
error = it as? BlockchainSdkError.Cardano ?: return@fold,
sendingCurrency = sendingCurrency,
sendingAmount = sendingAmount,
fee = fee,
)
},
ifRight = {
@ -455,8 +453,6 @@ internal class SendNotificationFactory(
private suspend fun MutableList<SendNotification>.addCardanoTransactionValidationError(
error: BlockchainSdkError.Cardano,
sendingCurrency: CryptoCurrency,
sendingAmount: BigDecimal,
fee: Fee,
) {
when (error) {
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
@ -473,9 +469,15 @@ internal class SendNotificationFactory(
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
-> {
addDustWarningNotification(
feeValue = fee.amount.value ?: BigDecimal.ZERO,
sendingAmount = sendingAmount,
val dustValue = currencyChecksRepository.getDustValue(
userWalletId = userWalletProvider().walletId,
network = sendingCurrency.network,
) ?: return
add(
SendNotification.Error.MinimumAmountError(
amount = dustValue.parseBigDecimal(sendingCurrency.decimals),
),
)
}
}

View file

@ -47,5 +47,6 @@ dependencies {
implementation(deps.arrow.core)
implementation(deps.timber)
implementation(deps.tangem.blockchain)
implementation(deps.tangem.card.core)
implementation(deps.moshi)
}

View file

@ -9,4 +9,13 @@ sealed class Warning {
data class MinAmountWarning(val dustValue: BigDecimal) : Warning()
data class ReduceAmountWarning(val tezosFeeThreshold: BigDecimal) : Warning()
sealed class Cardano : Warning() {
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Cardano()
data object InsufficientBalanceToTransferCoin : Cardano()
data class InsufficientBalanceToTransferToken(val tokenName: String) : Cardano()
}
}

View file

@ -5,9 +5,11 @@ import arrow.core.getOrElse
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
@ -21,6 +23,8 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
@ -36,15 +40,12 @@ import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.AnalyticsData
import com.tangem.lib.crypto.models.ApproveTxData
import com.tangem.lib.crypto.models.ProxyAmount
import com.tangem.lib.crypto.models.ProxyFees
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.isNullOrZero
import com.tangem.utils.toFiatString
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.firstOrNull
@ -73,6 +74,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val currenciesRepository: CurrenciesRepository,
private val initialToCurrencyResolver: InitialToCurrencyResolver,
private val demoConfig: DemoConfig,
private val transactionRepository: TransactionRepository,
) : SwapInteractor {
private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) {
@ -350,6 +352,7 @@ internal class SwapInteractorImpl @Inject constructor(
isAllowedToSpend = isAllowedToSpend,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
txFee = TxFeeState.Empty,
transactionFee = null,
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
)
}
@ -379,6 +382,7 @@ internal class SwapInteractorImpl @Inject constructor(
fromTokenStatus: CryptoCurrencyStatus,
amount: SwapAmount,
feeState: TxFeeState,
minAdaValue: BigDecimal?,
): List<Warning> {
val fromToken = fromTokenStatus.currency
val userWalletId = getSelectedWallet()?.walletId ?: return emptyList()
@ -386,6 +390,13 @@ internal class SwapInteractorImpl @Inject constructor(
manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken)
manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount)
manageReduceAmountWarning(warnings, fromTokenStatus, amount)
manageCardanoTransactionValidationWarnings(
warnings = warnings,
fromToken = fromToken,
amount = amount,
userWalletId = userWalletId,
minAdaValue = minAdaValue,
)
return warnings
}
@ -414,23 +425,35 @@ internal class SwapInteractorImpl @Inject constructor(
fromTokenStatus: CryptoCurrencyStatus,
amount: SwapAmount,
) {
if (BlockchainUtils.isCardano(fromTokenStatus.currency.network.id.value)) return
val fee = when (feeState) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
}
val dust = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network)
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
if (dust != null &&
!balance.isNullOrZero() &&
amount.value < balance
) {
val change = balance - (amount.value + fee)
val isChangeLowerThanDust = change < dust && change != BigDecimal.ZERO
val isShowWarning = amount.value + fee < dust || isChangeLowerThanDust
if (isShowWarning) {
warnings.add(Warning.MinAmountWarning(dust))
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) ?: return
val change = when (fromTokenStatus.currency) {
is CryptoCurrency.Coin -> {
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
balance - (fee + amount.value)
}
is CryptoCurrency.Token -> {
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
fromTokenStatus.currency.network.id.value,
fromTokenStatus.currency.network.derivationPath.value,
)
nativeTokenBalance?.value?.minus(fee) ?: BigDecimal.ZERO
}
}
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
if (amount.value < dustValue || isChangeLowerThanDust) {
warnings.add(Warning.MinAmountWarning(dustValue))
}
}
@ -445,6 +468,72 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private suspend fun manageCardanoTransactionValidationWarnings(
warnings: MutableList<Warning>,
fromToken: CryptoCurrency,
amount: SwapAmount,
userWalletId: UserWalletId,
minAdaValue: BigDecimal?,
) {
transactionRepository.validateTransaction(
amount = amount.value.convertToAmount(fromToken),
fee = null,
memo = null,
destination = getTokenAddress(fromToken),
userWalletId = userWalletId,
network = fromToken.network,
)
.fold(
onFailure = {
addCardanoTransactionValidationError(
warnings = warnings,
error = it as? BlockchainSdkError.Cardano ?: return@fold,
fromToken = fromToken,
userWalletId = userWalletId,
)
},
onSuccess = {
minAdaValue?.let {
warnings.add(
Warning.Cardano.MinAdaValueCharged(
tokenName = fromToken.name,
minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals),
),
)
}
},
)
}
private suspend fun addCardanoTransactionValidationError(
warnings: MutableList<Warning>,
error: BlockchainSdkError.Cardano,
fromToken: CryptoCurrency,
userWalletId: UserWalletId,
) {
when (error) {
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
}
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
when (fromToken) {
is CryptoCurrency.Coin -> Warning.Cardano.InsufficientBalanceToTransferCoin
is CryptoCurrency.Token -> {
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
}
}
}
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
-> {
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromToken.network) ?: return
Warning.MinAmountWarning(dustValue)
}
}
.let(warnings::add) // add warning to the list
}
override suspend fun onSwap(
swapProvider: SwapProvider,
swapData: SwapDataModel?,
@ -841,8 +930,16 @@ internal class SwapInteractorImpl @Inject constructor(
val fromToken = fromTokenStatus.currency
val toToken = toTokenStatus.currency
return coroutineScope {
val txFeeResult = getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
getUnhandledFee(
amount = amount.value,
userWalletId = userWalletId,
cryptoCurrency = fromToken,
)
}
val txFee = if (provider.type == ExchangeProviderType.CEX) {
getFeeForCex(amount, fromTokenStatus)
getFeeForCex(txFeeResult, fromTokenStatus)
} else {
TxFeeState.Empty
}
@ -881,11 +978,13 @@ internal class SwapInteractorImpl @Inject constructor(
isAllowedToSpend = isAllowedToSpend,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
txFee = txFee,
transactionFee = txFeeResult?.getOrNull(),
includeFeeInAmount = includeFeeInAmount,
)
}
}
@Suppress("LongMethod")
private suspend fun getQuotesState(
exchangeProviderType: ExchangeProviderType,
quoteDataModel: Either<DataError, QuoteModel>,
@ -896,6 +995,7 @@ internal class SwapInteractorImpl @Inject constructor(
isAllowedToSpend: Boolean,
isBalanceWithoutFeeEnough: Boolean,
txFee: TxFeeState,
transactionFee: TransactionFee?,
includeFeeInAmount: IncludeFeeInAmount,
): SwapState {
return quoteDataModel.fold(
@ -909,7 +1009,12 @@ internal class SwapInteractorImpl @Inject constructor(
swapData = null,
txFeeState = txFee,
).copy(
warnings = manageWarnings(fromToken, amount, txFee),
warnings = manageWarnings(
fromTokenStatus = fromToken,
amount = amount,
feeState = txFee,
minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue,
),
)
when (exchangeProviderType) {
@ -1113,7 +1218,14 @@ internal class SwapInteractorImpl @Inject constructor(
)
swapState.copy(
permissionState = PermissionDataState.Empty,
warnings = manageWarnings(fromToken, amount, txFeeState),
warnings = manageWarnings(
fromToken,
amount,
txFeeState,
(feeData as? ProxyFees.SingleFee)?.let {
(it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue
},
),
preparedSwapConfigState = PreparedSwapConfigState(
isAllowedToSpend = true,
isBalanceEnough = isBalanceIncludeFeeEnough,
@ -1179,23 +1291,26 @@ internal class SwapInteractorImpl @Inject constructor(
)
}
private suspend fun getFeeForCex(amount: SwapAmount, fromToken: CryptoCurrencyStatus): TxFeeState {
getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
val txFeeResult = estimateFeeUseCase(
amount = amount.value,
userWalletId = userWalletId,
cryptoCurrency = fromToken.currency,
).firstOrNull()
return txFeeResult?.fold(
ifLeft = {
TxFeeState.Empty
},
ifRight = { txFee ->
txFee.toTxFeeState(fromToken.currency)
},
) ?: TxFeeState.Empty
}
return TxFeeState.Empty
private suspend fun getFeeForCex(
txFeeResult: Either<GetFeeError, TransactionFee>?,
fromToken: CryptoCurrencyStatus,
): TxFeeState {
return txFeeResult?.fold(
ifLeft = { TxFeeState.Empty },
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) },
) ?: TxFeeState.Empty
}
private suspend fun getUnhandledFee(
amount: BigDecimal,
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Either<GetFeeError, TransactionFee>? {
return estimateFeeUseCase(
amount = amount,
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).firstOrNull()
}
@Suppress("LongParameterList", "LongMethod")

View file

@ -50,6 +50,7 @@ class SwapDomainModule {
coroutineDispatcherProvider: CoroutineDispatcherProvider,
initialToCurrencyResolver: InitialToCurrencyResolver,
currenciesRepository: CurrenciesRepository,
transactionRepository: TransactionRepository,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -69,6 +70,7 @@ class SwapDomainModule {
currenciesRepository = currenciesRepository,
initialToCurrencyResolver = initialToCurrencyResolver,
demoConfig = DemoConfig(),
transactionRepository = transactionRepository,
)
}

View file

@ -28,6 +28,7 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.transaction)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.settings)
@ -56,6 +57,9 @@ dependencies {
implementation(projects.features.swap.api)
implementation(projects.features.tokendetails.api)
/** Libs */
implementation(projects.libs.crypto)
/** Other libraries */
implementation(deps.compose.shimmer)
implementation(deps.compose.accompanist.webView)
@ -67,5 +71,4 @@ dependencies {
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -110,6 +110,7 @@ sealed interface SwapWarning {
val type: GenericWarningType = GenericWarningType.OTHER,
val onClick: () -> Unit,
) : SwapWarning
data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning
@ -117,6 +118,16 @@ sealed interface SwapWarning {
data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning
data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning
data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning
sealed interface Cardano : SwapWarning {
val notificationConfig: NotificationConfig
data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano
data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano
data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano
}
}
enum class GenericWarningType {

View file

@ -405,6 +405,15 @@ internal class StateBuilder(
),
)
}
Warning.Cardano.InsufficientBalanceToTransferCoin -> {
warnings.add(createInsufficientBalanceToTransferCoin())
}
is Warning.Cardano.InsufficientBalanceToTransferToken -> {
warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName))
}
is Warning.Cardano.MinAdaValueCharged -> {
warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName))
}
}
}
}
@ -1400,6 +1409,42 @@ internal class StateBuilder(
iconResId = R.drawable.img_attention_20,
)
}
private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning {
return SwapWarning.Cardano.MinAdaValueCharged(
NotificationConfig(
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
subtitle = resourceReference(
id = R.string.cardano_coin_will_be_send_with_token_description,
formatArgs = wrappedList(minAdaValue, tokenName),
),
iconResId = R.drawable.img_attention_20,
),
)
}
private fun createInsufficientBalanceToTransferCoin(): SwapWarning {
return SwapWarning.Cardano.InsufficientBalanceToTransferCoin(
NotificationConfig(
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
iconResId = R.drawable.img_attention_20,
),
)
}
private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning {
return SwapWarning.Cardano.InsufficientBalanceToTransferToken(
NotificationConfig(
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
subtitle = resourceReference(
id = R.string.cardano_insufficient_balance_to_send_token_description,
formatArgs = wrappedList(tokenName),
),
iconResId = R.drawable.img_attention_20,
),
)
}
// end region
private fun getShortAddressValue(fullAddress: String): String {

View file

@ -391,7 +391,8 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
},
)
}
else -> {}
is SwapWarning.Cardano -> Notification(config = warning.notificationConfig)
SwapWarning.InsufficientFunds -> Unit
}
SpacerH8()
}

View file

@ -1,8 +1,21 @@
package com.tangem.lib.crypto.models
import java.math.BigDecimal
import java.math.BigInteger
data class ProxyFee(
val gasLimit: BigInteger,
val fee: ProxyAmount,
)
sealed interface ProxyFee {
val gasLimit: BigInteger
val fee: ProxyAmount
data class Common(
override val gasLimit: BigInteger,
override val fee: ProxyAmount,
) : ProxyFee
data class CardanoToken(
override val gasLimit: BigInteger,
override val fee: ProxyAmount,
val minAdaValue: BigDecimal,
) : ProxyFee
}