Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-26 13:25:02 +03:00
parent e50716f834
commit 2bb8415d4d
9 changed files with 92 additions and 59 deletions

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.SwapApproveType
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.ui.*
interface SwapInteractor {
@ -40,19 +40,10 @@ interface SwapInteractor {
* Gives permission to swap, this starts scan card process
*
* @param networkId network in which selected token
* @param approveData tx data to give approve, it loaded from 1inch in findBestQuote if needed
* @param forTokenContractAddress token contract address for which needs permission
* @param fromToken which token will be swapping
* @param approveType unlimited or tx amount approve
* @param permissionOptions data to give permissions
*/
@Throws(IllegalStateException::class)
suspend fun givePermissionToSwap(
networkId: String,
approveData: RequestApproveStateData,
forTokenContractAddress: String,
fromToken: Currency,
approveType: SwapApproveType,
): TxState
suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState
/**
* Find best quote for given tokens to swap

View file

@ -5,6 +5,7 @@ import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState
import com.tangem.feature.swap.domain.models.domain.SwapApproveType
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
@ -107,29 +108,23 @@ internal class SwapInteractorImpl @Inject constructor(
?: loadedTokens.firstOrNull { it.token.id == id }?.token
}
override suspend fun givePermissionToSwap(
networkId: String,
approveData: RequestApproveStateData,
forTokenContractAddress: String,
fromToken: Currency,
approveType: SwapApproveType,
): TxState {
val dataToSign = if (approveType == SwapApproveType.UNLIMITED) {
repository.dataToApprove(networkId, getTokenAddress(fromToken)).data
override suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState {
val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) {
repository.dataToApprove(networkId, getTokenAddress(permissionOptions.fromToken)).data
} else {
approveData.approveModel.data
permissionOptions.approveData.approveModel.data
}
val result = transactionManager.sendApproveTransaction(
networkId = networkId,
feeAmount = approveData.fee,
gasLimit = approveData.gasLimit,
destinationAddress = approveData.approveModel.toAddress,
feeAmount = permissionOptions.txFee.feeValue,
gasLimit = permissionOptions.txFee.gasLimit,
destinationAddress = permissionOptions.approveData.approveModel.toAddress,
dataToSign = dataToSign,
derivationPath = derivationPath,
)
return when (result) {
is SendTxResult.Success -> {
allowPermissionsHandler.addAddressToInProgress(forTokenContractAddress)
allowPermissionsHandler.addAddressToInProgress(permissionOptions.forTokenContractAddress)
TxState.TxSent(txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath) ?: "")
}
SendTxResult.UserCancelledError -> TxState.UserCancelled
@ -414,7 +409,7 @@ internal class SwapInteractorImpl @Inject constructor(
val isBalanceIncludeFeeEnough =
isBalanceEnough(networkId, fromToken, amount, txFeeState.priorityFee.feeValue)
val isFeeEnough = checkFeeIsEnough(
fee = txFeeState.priorityFee.feeValue,
fee = txFeeState.normalFee.feeValue,
spendAmount = amount,
networkId = networkId,
fromToken = fromToken,
@ -527,12 +522,7 @@ internal class SwapInteractorImpl @Inject constructor(
data = transactionData.data,
derivationPath = derivationPath,
)
val feeFiat = getFormattedFiatFees(networkId, feeData.normalFee.fee.value)
val formattedFee = amountFormatter.formatBigDecimalAmountToUI(
amount = feeData.normalFee.fee.value,
decimals = transactionManager.getNativeTokenDecimals(networkId),
currency = userWalletManager.getNetworkCurrency(networkId),
) + (feeFiat.firstOrNull() ?: "")
val feeState = proxyFeesToFeeState(networkId, feeData)
val isFeeEnough = checkFeeIsEnough(
fee = feeData.normalFee.fee.value,
spendAmount = SwapAmount.zeroSwapAmount(),
@ -545,10 +535,8 @@ internal class SwapInteractorImpl @Inject constructor(
amount = INFINITY_SYMBOL,
walletAddress = getWalletAddress(networkId),
spenderAddress = transactionData.toAddress,
fee = formattedFee,
requestApproveData = RequestApproveStateData(
fee = feeData.normalFee.fee.value,
gasLimit = feeData.normalFee.gasLimit.toInt(),
fee = feeState,
approveModel = transactionData,
),
),

View file

@ -0,0 +1,21 @@
package com.tangem.feature.swap.domain.models.domain
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
import com.tangem.feature.swap.domain.models.ui.TxFee
/**
* Permission options
*
* @param approveData tx data to give approve, it loaded from 1inch in findBestQuote if needed
* @param forTokenContractAddress token contract address for which needs permission
* @param fromToken which token will be swapping
* @param approveType unlimited or tx amount approve
* @param txFee fee for tx
*/
data class PermissionOptions(
val approveData: RequestApproveStateData,
val forTokenContractAddress: String,
val fromToken: Currency,
val approveType: SwapApproveType,
val txFee: TxFee,
)

View file

@ -40,7 +40,6 @@ sealed class PermissionDataState {
val amount: String,
val walletAddress: String,
val spenderAddress: String,
val fee: String,
val requestApproveData: RequestApproveStateData,
) : PermissionDataState()
@ -59,8 +58,7 @@ data class TokenSwapInfo(
)
data class RequestApproveStateData(
val fee: BigDecimal,
val gasLimit: Int,
val fee: TxFeeState,
val approveModel: ApproveModel,
)

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.models
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.swap.domain.models.domain.SwapApproveType
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@ -15,7 +16,7 @@ sealed class SwapPermissionState {
val amount: String,
val walletAddress: String,
val spenderAddress: String,
val fee: String,
val fee: TextReference,
val approveType: ApproveType,
val approveItems: ImmutableList<ApproveType> = ApproveType.values().toList().toImmutableList(),
val approveButton: ApprovePermissionButton,

View file

@ -57,17 +57,21 @@ sealed class FeeState(open val tangemFee: Double) {
data class Loaded(
override val tangemFee: Double,
val state: SelectableItemsState<TxFee>?,
override val state: SelectableItemsState<TxFee>?,
val onSelectItem: (Item<TxFee>) -> Unit,
) : FeeState(tangemFee)
) : FeeState(tangemFee), FeeSelectState
object Loading : FeeState(0.0)
data class NotEnoughFundsWarning(
override val tangemFee: Double,
val state: SelectableItemsState<TxFee>?,
override val state: SelectableItemsState<TxFee>?,
val onSelectItem: (Item<TxFee>) -> Unit,
) : FeeState(tangemFee)
) : FeeState(tangemFee), FeeSelectState
}
sealed interface FeeSelectState {
val state: SelectableItemsState<TxFee>?
}
sealed interface TransactionCardType {

View file

@ -161,6 +161,7 @@ internal class StateBuilder(val actions: UiActions) {
permissionState = convertPermissionState(
lastPermissionState = uiStateHolder.permissionState,
permissionDataState = quoteModel.permissionState,
feeState = feeState,
onGivePermissionClick = actions.onGivePermissionClick,
onChangeApproveType = actions.onChangeApproveType,
),
@ -269,6 +270,14 @@ internal class StateBuilder(val actions: UiActions) {
val newSelectedItem = item.copy(
startText = TextReference.Res(R.string.send_network_fee_title),
)
val permissionState = uiState.permissionState
val newPermissionState = if (permissionState is SwapPermissionState.ReadyForRequest) {
permissionState.copy(
fee = newSelectedItem.endText,
)
} else {
permissionState
}
return when (val fee = uiState.fee) {
is FeeState.Loaded -> {
val newState = fee.state?.copy(
@ -277,6 +286,7 @@ internal class StateBuilder(val actions: UiActions) {
)
uiState.copy(
fee = fee.copy(state = newState),
permissionState = newPermissionState,
)
}
is FeeState.NotEnoughFundsWarning -> {
@ -286,6 +296,7 @@ internal class StateBuilder(val actions: UiActions) {
)
uiState.copy(
fee = fee.copy(state = newState),
permissionState = newPermissionState,
)
}
else -> uiState
@ -302,8 +313,14 @@ internal class StateBuilder(val actions: UiActions) {
is FeeState.NotEnoughFundsWarning -> stateFee.state
else -> null
}
val permissionState = quoteModel.permissionState
val feeState = if (permissionState is PermissionDataState.PermissionReadyForRequest) {
permissionState.requestApproveData.fee
} else {
quoteModel.swapDataModel?.fee
}
val selectFeeState = createSelectFeeState(
fee = quoteModel.swapDataModel?.fee,
fee = feeState,
previousState = previousFeeState,
onFeeSetup = onFeeSetup,
)
@ -407,6 +424,7 @@ internal class StateBuilder(val actions: UiActions) {
private fun convertPermissionState(
lastPermissionState: SwapPermissionState,
permissionDataState: PermissionDataState,
feeState: FeeState,
onGivePermissionClick: () -> Unit,
onChangeApproveType: (ApproveType) -> Unit,
): SwapPermissionState {
@ -415,6 +433,10 @@ internal class StateBuilder(val actions: UiActions) {
} else {
ApproveType.UNLIMITED
}
val fee = when (feeState) {
is FeeSelectState -> feeState.state?.selectedItem?.endText
else -> null
}
return when (permissionDataState) {
PermissionDataState.Empty -> SwapPermissionState.Empty
PermissionDataState.PermissionFailed -> SwapPermissionState.Empty
@ -425,7 +447,7 @@ internal class StateBuilder(val actions: UiActions) {
approveType = approveType,
walletAddress = getShortAddressValue(permissionDataState.walletAddress),
spenderAddress = getShortAddressValue(permissionDataState.spenderAddress),
fee = permissionDataState.fee,
fee = fee ?: TextReference.Str(""),
approveButton = ApprovePermissionButton(
enabled = true,
onClick = onGivePermissionClick,

View file

@ -13,6 +13,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.ApprovePermissionButton
import com.tangem.feature.swap.models.ApproveType
@ -123,7 +125,7 @@ private fun ApprovalBottomSheetInfo(data: SwapPermissionState.ReadyForRequest) {
)
SpacerH24()
DividerBottomSheet()
FeeItem(fee = data.fee)
FeeItem(fee = data.fee.resolveReference())
SubtitleItem(
subtitle = stringResource(id = R.string.swapping_permission_fee_footer),
modifier = Modifier.fillMaxWidth(),
@ -303,7 +305,7 @@ private val previewData = SwapPermissionState.ReadyForRequest(
amount = "",
walletAddress = "",
spenderAddress = "",
fee = "2,14$",
fee = TextReference.Str("2,14$"),
approveType = ApproveType.UNLIMITED,
approveButton = ApprovePermissionButton(true) {},
cancelButton = CancelPermissionButton(true),

View file

@ -12,6 +12,7 @@ import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.models.SwapPermissionState
@ -276,17 +277,22 @@ internal class SwapViewModel @Inject constructor(
runCatching(dispatchers.io) {
swapInteractor.givePermissionToSwap(
networkId = dataState.networkId,
approveData = requireNotNull(dataState.approveDataModel) {
Timber.e("dataState.approveDataModel might not be null")
},
forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress
?: "",
fromToken = requireNotNull(dataState.fromCurrency) {
Timber.e("dataState.fromCurrency might not be null")
},
approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) {
Timber.e("uiState.permissionState should be SwapPermissionState.ReadyForRequest")
}.approveType.toDomainApproveType(),
permissionOptions = PermissionOptions(
approveData = requireNotNull(dataState.approveDataModel) {
"dataState.approveDataModel might not be null"
},
forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress
?: "",
fromToken = requireNotNull(dataState.fromCurrency) {
"dataState.fromCurrency might not be null"
},
approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) {
"uiState.permissionState should be SwapPermissionState.ReadyForRequest"
}.approveType.toDomainApproveType(),
txFee = requireNotNull(dataState.selectedFee) {
"dataState.selectedFee shouldn't be null"
},
),
)
}
.onSuccess {