From d22bfd0c87d8fe557bb8efad3d3644f7a6b4c3d8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Apr 2026 15:15:08 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../GiveTxPermisssionBottomSheet.kt | 1 + .../permission/state/GiveTxPermissionState.kt | 1 + core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 2 +- core/res/src/main/res/values-fr/strings.xml | 2 +- core/res/src/main/res/values-it/strings.xml | 2 +- core/res/src/main/res/values-ja/strings.xml | 26 ++- .../src/main/res/values-pt-rBR/strings.xml | 2 +- core/res/src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-uk-rUA/strings.xml | 2 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 7 +- .../approval/api/GiveApprovalComponent.kt | 1 + .../impl/DefaultGiveApprovalComponent.kt | 20 +- .../approval/impl/model/GiveApprovalModel.kt | 213 ++++++++++++++---- .../approval/impl/model/GiveApprovalUM.kt | 1 + .../approval/impl/ui/GiveApprovalContent.kt | 105 +++++---- .../send/v2/api/params/FeeSelectorParams.kt | 3 + .../DefaultFeeSelectorBlockComponent.kt | 1 + .../v2/feeselector/model/FeeSelectorLogic.kt | 1 + .../model/transformers/FeeItemConverter.kt | 7 +- .../FeeSelectorLoadedTransformer.kt | 2 + .../ShowApprovalBottomSheetTransformer.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 60 ++--- .../swap/domain/models/ui/SwapState.kt | 1 + .../feature/swap/DefaultSwapComponent.kt | 13 +- .../tangem/feature/swap/ui/StateBuilder.kt | 1 + 27 files changed, 342 insertions(+), 139 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 7af3019d50..0c56512c7c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -330,6 +330,7 @@ private val previewData = GiveTxPermissionBottomSheetConfig( approveType = ApproveType.LIMITED, approveButton = ApprovePermissionButton(true) {}, cancelButton = CancelPermissionButton(true), + isResetApproval = false, onChangeApproveType = { ApproveType.LIMITED }, subtitle = resourceReference(R.string.give_permission_staking_subtitle, wrappedList("1")), dialogText = resourceReference(R.string.give_permission_staking_footer), diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt index baa901aeba..5b4c90cc6c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt @@ -25,6 +25,7 @@ sealed class GiveTxPermissionState { val approveItems: ImmutableList = ApproveType.entries.toImmutableList(), val approveButton: ApprovePermissionButton, val cancelButton: CancelPermissionButton, + val isResetApproval: Boolean, val onChangeApproveType: ((ApproveType) -> Unit)? = null, val onOpenLearnMoreAboutApproveClick: () -> Unit, ) : GiveTxPermissionState() diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 5132421c98..07bf709771 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1620,7 +1620,7 @@ Service vorübergehend nicht verfügbar Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin. Satz \nPIN-Code - Nicht synchronisiert + Sitzung abgelaufen Zugang wiederherstellen Nutzen Sie USDC für alltägliche Zahlungen Tangem Pay ist vorübergehend nicht erreichbar. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 1b40ed9ebf..b257cbe438 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1593,7 +1593,7 @@ Servicio temporalmente no disponible No es posible mostrar los datos en este momento, pero los pagos con tarjeta siguen funcionando. Establecer \nCódigo PIN - Sincronización necesaria + Sesión expirada Restablecer acceso Usa USDC para pagos cotidianos Tangem Pay temporalmente no disponible diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 772f4d92d2..9075ee073a 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1590,7 +1590,7 @@ Service temporairement indisponible Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours. Définir le \ncode PIN - Synchronisation requise + Session expirée Restaurer l\'accès Utilisez USDC pour les paiements quotidiens Tangem Pay est temporairement indisponible diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 0282ef7661..917756add9 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -177,7 +177,7 @@ Stiamo risolvendo un problema tecnico. Riprova più tardi. Servizio temporaneamente non disponibile Al momento non è possibile visualizzare i dati, ma i pagamenti con carta continuano a funzionare. - Sincronizzazione necessaria + Sessione scaduta Usa USDC per i pagamenti quotidiani Tangem Pay è temporaneamente non disponibile Tangem Pay diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 6dadf3d9d5..404b67969c 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -467,6 +467,19 @@ 他のネットワークで資産を送金すると、永久に失われます。 %sネットワーク 下記のみを使用して資金を送金する + 動的アドレス + 動的アドレスが有効になっています。カスタムの「change」と「index」は利用できません。 + 動的アドレスが有効です。 + 取引ごとに新しいアドレスを使うことで、追跡されにくくなり、オンチェーン上のプライバシーが向上します。 + プライバシー強化 + UTXOベースのネットワークでは、アドレスが自動生成されるため、手動で管理しなくても簡単に資金を受け取れます。 + スムーズな受け取り + 動的アドレスを有効にする + 動的アドレスでは、プライバシー強化のため毎回新しいアドレスが生成されますが、合計残高は変わりません。 + 一部のカスタムアドレスまたはトークンで通常と異なる導出パスが使われているため、動的アドレスを有効にできません。 + 動的アドレスは利用できません + 現在、プロバイダーに接続できません。しばらくしてからもう一度お試しください。 + サービスを利用できません。しばらくしてからもう一度お試しください。 おすすめ 絞り込みを解除 リストは現在更新中のため、一時的に空になっています。しばらくしてからご確認ください。 @@ -745,7 +758,10 @@ データなし マーケット動向 クイックアクション + すべてクリア トークンを探す + 最近 + ポートフォリオ内 結果 時価総額10万ドル以下のトークンを見る トークンを表示 @@ -820,9 +836,11 @@ 時価総額に基づくすべてのコイン間の暗号資産評価における位置 市場格付け 最大供給量 + 流通量と最大供給量 特定の暗号資産に存在しうるコインまたはトークンの最大数 最大供給量 指標 + 制限なし 公式リンク 値動き リポジトリ @@ -1095,6 +1113,8 @@ 認識できないQRコード このネットワークは、追加されているトークンのいずれにも対応していません。対応しているトークンを追加してから暗号資産を送金してください。 対応しているトークンが見つかりません + このQRコードには認識できないパラメータが含まれています:%s。続行すると、一部の支払い情報が失われる可能性があります。 + 不明なパラメータ メモ不要 %3$sネットワーク上の%1$s ( %2$s ) %2$sネットワーク上の%1$s @@ -1477,6 +1497,7 @@ 手数料見積りエラーです。サポートにフィードバックをお送りください。 スワップする 選択したトークンをこの量を交換すると、価格に大きな影響が生じ、結果が減少します。 + 流動性が低いため、受取額が大幅に少なくなる可能性があります。金額を減らすか、別のプロバイダーをお試しください。 価格への影響が甚大です 残高不足 この取引を完了するには残高が不足しています。受け取り額を減らすか、資金を追加してください。 @@ -1486,6 +1507,8 @@ 受け取る トークンを選択 利用不可 + この取引に十分な流動性がありません。\n金額を減らすか、別のプロバイダーを選択してください。 + 取引額が大きすぎます 皆様からのフィードバックをお待ちしております Tangem Payのベータ版を公開しました カードが凍結されています @@ -1609,7 +1632,7 @@ サービスは一時的に利用できません 現在、データを表示できませんが、カードでのお支払いは引き続きご利用いただけます。 \nPINコードの設定 - 未同期 + セッションの有効期限が切れました アクセスを復元 日常の支払いにUSDCを利用 Tangem Payは現在一時的に利用できません。 @@ -1646,6 +1669,7 @@ %%image%% %1$sネットワーク上のトークン %1$s ( %2$s ) トークンは%3$sネットワークの主要通貨であり、このネットワーク上の他のトークンがリストにある限り、非表示にすることはできません。 %sを非表示にできません + 該当なし QRコードを表示 このトークンは、2月%2$s-%3$s の間、%1$s のサービス手数料で別のトークンと交換できます。 Changellyでスワップ、手数料%s diff --git a/core/res/src/main/res/values-pt-rBR/strings.xml b/core/res/src/main/res/values-pt-rBR/strings.xml index c6642f4bfa..94c47b4783 100644 --- a/core/res/src/main/res/values-pt-rBR/strings.xml +++ b/core/res/src/main/res/values-pt-rBR/strings.xml @@ -1631,7 +1631,7 @@ Serviço temporariamente indisponível Não foi possível exibir os detalhes. No entanto, os pagamentos com cartão ainda estão funcionando. Defina o código PIN. - Não sincronizado + Sessão expirada Restaurar acesso Use USDC para pagamentos do dia a dia. O serviço Tangem Pay está temporariamente inacessível. diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 2654c8ae24..67dfaf1848 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1624,7 +1624,7 @@ Мы устраняем техническую проблему. Пожалуйста, попробуйте позже. Сервис временно недоступен Не можем показать данные карты, но оплаты продолжают работать. - Не синхронизирован + Сессия истекла Оплачивайте ежедневные покупки в USDC Tangem Pay временно недоступен Tangem Pay diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 67cdf8af99..8df7a978d6 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1633,7 +1633,7 @@ Сервіс тимчасово недоступний Не можемо показати дані картки, але оплати продовжують працювати. Встановіть \nPIN-код - Не синхронізовано + Сесія закінчилася Відновити доступ Використовуйте USDC для щоденних платежів Tangem Pay тимчасово недоступний diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index c18d1cec74..a6205964b9 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -419,7 +419,7 @@ 我们正在修复技术问题。请稍后再试。 服務暫時無法使用 目前無法顯示資料,但卡片支付仍可正常使用。 - 需要同步 + 工作階段已過期 使用 USDC 進行日常支付 Tangem Pay暂时不可用 Tangem Pay diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 53ed50688f..d3754c9d92 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -422,6 +422,7 @@ Token already exists Decimal must be a valid integer, up to %d Custom derivation + Dynamic Addresses is enabled. Custom \\"change\\" and \\"index\\" are unavailable. E. g. m/00\'/0000\'/0\'/0/0 Enter custom derivation Decimals @@ -475,6 +476,7 @@ %s network Send funds using only Dynamic addresses + Dynamic Addresses is enabled. Custom \"change\" and \"index\" are unavailable. Dynamic addresses enabled Use a new address for each transaction to reduce traceability and improve on-chain privacy. Enhanced Privacy @@ -1652,7 +1654,7 @@ Service temporarily unavailable Unable to display details. However, card payments are still working. Set \nPIN code - Not synced + Session expired Restore access Use USDC for everyday payments Tangem Pay is temporarily unreachable @@ -1732,6 +1734,9 @@ We\'ve encountered an error. Error code: %s. Please contact our support. Use %s or scan a card/ring to have access to your wallet Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully. + Previous permission will be revoked and a new one will be issued. Network will charge token approval fee for each of theses actions. You will see a zero-amount transaction in the history as a revoke evidence. + Transaction exceeds previously granted permission amount.\nUpdate permission to proceed + Update permission Upgrade to hardware wallet Stay up to date with the latest features and news Real-time alerts for transactions, exchanges, and critical updates. diff --git a/features/approval/api/src/main/java/com/tangem/features/approval/api/GiveApprovalComponent.kt b/features/approval/api/src/main/java/com/tangem/features/approval/api/GiveApprovalComponent.kt index 10506d2ba5..cb5db01145 100644 --- a/features/approval/api/src/main/java/com/tangem/features/approval/api/GiveApprovalComponent.kt +++ b/features/approval/api/src/main/java/com/tangem/features/approval/api/GiveApprovalComponent.kt @@ -16,6 +16,7 @@ interface GiveApprovalComponent : ComposableBottomSheetComponent { val spenderAddress: String, val subtitle: TextReference, val isHoldToConfirm: Boolean = false, + val isResetApproval: Boolean = false, val callback: Callback, ) diff --git a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/DefaultGiveApprovalComponent.kt b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/DefaultGiveApprovalComponent.kt index 756d19c2be..21fe5969b9 100644 --- a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/DefaultGiveApprovalComponent.kt +++ b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/DefaultGiveApprovalComponent.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -23,7 +24,6 @@ import com.tangem.features.send.v2.api.params.FeeSelectorParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import com.tangem.common.ui.R as CommonUiR internal class DefaultGiveApprovalComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @@ -38,6 +38,7 @@ internal class DefaultGiveApprovalComponent @AssistedInject constructor( params = FeeSelectorParams.FeeSelectorBlockParams( state = FeeSelectorUM.Loading, onLoadFee = { model.loadFee() }, + onDisableCustomFee = { model.shouldDisableCustomFee() }, onLoadFeeExtended = { selectedFeeToken -> model.loadFeeExtended(selectedFeeToken) }, feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus, @@ -71,22 +72,23 @@ internal class DefaultGiveApprovalComponent @AssistedInject constructor( TangemBottomSheet( config = config, containerColor = TangemTheme.colors.background.secondary, - titleText = resourceReference(CommonUiR.string.give_permission_title), + titleText = resourceReference( + if (uiState.isResetApproval) { + R.string.update_approval_permission_title + } else { + R.string.give_permission_title + }, + ), titleAction = TopAppBarButtonUM.Icon( - iconRes = CommonUiR.drawable.ic_information_24, + iconRes = R.drawable.ic_information_24, onClicked = model::showPermissionInfoDialog, ), ) { GiveApprovalContent( currency = currency, subtitle = params.subtitle, - approveType = uiState.approveType, - approveItems = uiState.approveItems, + uiState = uiState, onChangeApproveType = model::onChangeApproveType, - walletInteractionIcon = uiState.walletInteractionIcon, - isApproveEnabled = uiState.isApproveButtonEnabled, - isApproveLoading = uiState.isApproveLoading, - isHoldToConfirm = uiState.isHoldToConfirm, onApproveClick = model::onApproveClick, onCancelClick = model::onCancelClick, onOpenLearnMoreAboutApproveClick = model::onOpenLearnMoreAboutApproveClick, diff --git a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalModel.kt b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalModel.kt index 9873b3d65a..585086e50e 100644 --- a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalModel.kt +++ b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalModel.kt @@ -5,6 +5,7 @@ import arrow.core.Either import arrow.core.getOrElse import arrow.core.left import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSender.MultipleTransactionSendMode import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.ui.bottomsheet.permission.state.ApproveType @@ -22,8 +23,10 @@ import com.tangem.core.ui.message.DialogMessage import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.models.AllowanceInfo import com.tangem.domain.transaction.models.TransactionFeeExtended import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase +import com.tangem.domain.transaction.usecase.GetAllowanceInfoUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase @@ -32,6 +35,7 @@ import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.approval.api.GiveApprovalComponent import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback +import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -41,15 +45,17 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.math.BigDecimal +import java.math.RoundingMode import javax.inject.Inject @Stable @ModelScoped -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class GiveApprovalModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, paramsContainer: ParamsContainer, private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase, + private val getAllowanceInfoUseCase: GetAllowanceInfoUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val getFeeUseCase: GetFeeUseCase, private val getFeeForGaslessUseCase: GetFeeForGaslessUseCase, @@ -77,11 +83,14 @@ internal class GiveApprovalModel @Inject constructor( isApproveButtonEnabled = false, isApproveLoading = false, isHoldToConfirm = params.isHoldToConfirm, + isResetApproval = params.isResetApproval, ), ) private var feeSelectorUM: FeeSelectorUM = FeeSelectorUM.Loading + private var approvalTxList: Map = emptyMap() + override fun onFeeResult(feeSelectorUM: FeeSelectorUM) { this.feeSelectorUM = feeSelectorUM uiState.update { it.copy(isApproveButtonEnabled = feeSelectorUM.isPrimaryButtonEnabled) } @@ -122,83 +131,127 @@ internal class GiveApprovalModel @Inject constructor( ) } - suspend fun prepareApprovalTransaction(): Either { - val cryptoCurrencyStatus = params.cryptoCurrencyStatus - val tokenCurrency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token - ?: return Either.Left(IllegalStateException("Currency is not a token")) - - return createApprovalTransactionUseCase( - cryptoCurrencyStatus = cryptoCurrencyStatus, - userWalletId = params.userWalletId, - amount = getApprovalAmount(), - contractAddress = tokenCurrency.contractAddress, - spenderAddress = params.spenderAddress, + suspend fun loadFee(): Either { + return onApprovalTx( + onApprove = { approve -> + getFeeUseCase( + transactionData = approve, + userWallet = userWallet, + network = params.cryptoCurrencyStatus.currency.network, + ).onRight { fee -> + approvalTxList = mapOf(approve to fee) + } + }, + onResetApprove = { (revokeApproval, approve) -> + getFeeUseCase( + transactionData = revokeApproval, + userWallet = userWallet, + network = params.cryptoCurrencyStatus.currency.network, + ).map { revokeFee -> + estimateFeeForResetApproval( + revokeTransactionFee = revokeFee, + revokeApprovalTransaction = revokeApproval, + approvalTransaction = approve, + ) + } + }, ) } - suspend fun loadFee(): Either { - val approvalTransaction = prepareApprovalTransaction() - .getOrElse { return GetFeeError.DataError(it).left() } - - return getFeeUseCase( - transactionData = approvalTransaction, - userWallet = userWallet, - network = params.cryptoCurrencyStatus.currency.network, - ) + fun shouldDisableCustomFee(): Boolean { + return approvalTxList.size > 1 } suspend fun loadFeeExtended(maybeToken: CryptoCurrencyStatus?): Either { - val approvalTransaction = prepareApprovalTransaction() - .getOrElse { return GetFeeError.DataError(it).left() } + val approve = createApprovalTransactionUseCase( + userWalletId = params.userWalletId, + cryptoCurrencyStatus = params.cryptoCurrencyStatus, + amount = getApprovalAmount(), + contractAddress = (params.cryptoCurrencyStatus.currency as CryptoCurrency.Token).contractAddress, + spenderAddress = params.spenderAddress, + ).getOrElse { error -> + TangemLogger.e("Failed to create approveTransaction", error) + return GetFeeError.DataError(error).left() + } return if (maybeToken == null) { getFeeForGaslessUseCase( - transactionData = approvalTransaction, + transactionData = approve, userWallet = userWallet, network = params.cryptoCurrencyStatus.currency.network, ) } else { getFeeForTokenUseCase( - transactionData = approvalTransaction, + transactionData = approve, userWallet = userWallet, token = maybeToken.currency, ) } } + private fun estimateFeeForResetApproval( + revokeTransactionFee: TransactionFee, + revokeApprovalTransaction: TransactionData.Uncompiled, + approvalTransaction: TransactionData.Uncompiled, + ) = when (revokeTransactionFee) { + is TransactionFee.Choosable -> { + val approveFee = revokeTransactionFee.copy( + minimum = revokeTransactionFee.minimum.increaseEthereumGasLimitBy(2.toBigDecimal()), + normal = revokeTransactionFee.normal.increaseEthereumGasLimitBy(2.toBigDecimal()), + priority = revokeTransactionFee.priority.increaseEthereumGasLimitBy(2.toBigDecimal()), + ) + approvalTxList = mapOf( + revokeApprovalTransaction to revokeTransactionFee, + approvalTransaction to approveFee, + ) + revokeTransactionFee.copy( + minimum = approveFee.minimum + revokeTransactionFee.minimum, + normal = approveFee.normal + revokeTransactionFee.normal, + priority = approveFee.priority + revokeTransactionFee.priority, + ) + } + is TransactionFee.Single -> { + val approveFee = revokeTransactionFee.copy(normal = revokeTransactionFee.normal) + approvalTxList = mapOf( + revokeApprovalTransaction to revokeTransactionFee, + approvalTransaction to approveFee, + ) + revokeTransactionFee.copy(normal = approveFee.normal + revokeTransactionFee.normal) + } + } + private suspend fun sendApprovalTransaction(): Boolean { val cryptoCurrencyStatus = params.cryptoCurrencyStatus val tokenCurrency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return false val feeContent = feeSelectorUM as? FeeSelectorUM.Content ?: return false - val selectedFee = feeContent.selectedFeeItem.fee val feeExtended = feeContent.feeExtraInfo.transactionFeeExtended val isFeeInTokenCurrency = feeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency - val transactionData = createApprovalTransactionUseCase( - cryptoCurrencyStatus = cryptoCurrencyStatus, - userWalletId = params.userWalletId, - amount = getApprovalAmount(), - fee = selectedFee, - contractAddress = tokenCurrency.contractAddress, - spenderAddress = params.spenderAddress, - ).getOrElse { error -> - TangemLogger.e("Failed to create approval transaction", error) - return false + val transactions = approvalTxList.map { (tx, fee) -> + tx.copy( + fee = when (feeContent.selectedFeeItem) { + is FeeItem.Fast -> (fee as? TransactionFee.Choosable)?.priority ?: fee.normal + is FeeItem.Market -> fee.normal + is FeeItem.Slow -> (fee as? TransactionFee.Choosable)?.minimum ?: fee.normal + else -> feeContent.selectedFeeItem.fee + }, + ) } return if (isFeeInTokenCurrency) { createAndSendGaslessTransactionUseCase( userWallet = userWallet, - transactionData = transactionData, + transactionData = transactions.first(), fee = feeExtended, ) } else { sendTransactionUseCase( - txData = transactionData, + txsData = transactions, userWallet = userWallet, network = tokenCurrency.network, + sendMode = MultipleTransactionSendMode.DEFAULT, ) }.fold( ifLeft = { error -> @@ -241,4 +294,88 @@ internal class GiveApprovalModel @Inject constructor( null } } + + private suspend fun onApprovalTx( + onApprove: suspend (TransactionData.Uncompiled) -> Either, + onResetApprove: + suspend (Pair) -> Either, + ): Either { + val cryptoCurrencyStatus = params.cryptoCurrencyStatus + val tokenCurrency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token + ?: return GetFeeError.DataError(IllegalStateException("Currency is not a token")).left() + + val amount = params.amount.toBigDecimalOrNull() + ?: return GetFeeError.DataError(IllegalArgumentException("Invalid amount format")).left() + + val allowance = getAllowanceInfoUseCase( + userWalletId = params.userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + spenderAddress = params.spenderAddress, + requiredAmount = amount, + ).getOrElse { error -> + TangemLogger.e("Failed to get allowance info", error) + return GetFeeError.DataError(error).left() + } + + val approve = createApprovalTransactionUseCase( + userWalletId = params.userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amount = getApprovalAmount(), + contractAddress = tokenCurrency.contractAddress, + spenderAddress = params.spenderAddress, + ).getOrElse { error -> + TangemLogger.e("Failed to create approveTransaction", error) + return GetFeeError.DataError(error).left() + } + + return if (allowance is AllowanceInfo.ResetNeeded) { + val revokeApproval = createApprovalTransactionUseCase( + userWalletId = params.userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amount = BigDecimal.ZERO, + contractAddress = tokenCurrency.contractAddress, + spenderAddress = params.spenderAddress, + ).getOrElse { error -> + TangemLogger.e("Failed to create revoke approveTransaction", error) + return GetFeeError.DataError(error).left() + } + onResetApprove(revokeApproval to approve) + } else { + onApprove(approve) + } + } + + private fun Fee.increaseEthereumGasLimitBy(multiplier: BigDecimal): Fee { + if (this !is Fee.Ethereum) return this + val increasedGasPrice = amount.value?.movePointRight(amount.decimals) + ?.divide(gasLimit.toBigDecimal(), RoundingMode.HALF_UP) + val increasedGasLimit = gasLimit + .multiply(multiplier.toBigInteger()) + val increasedAmount = amount.copy( + value = increasedGasPrice?.multiply( + increasedGasLimit.toBigDecimal().movePointLeft(amount.decimals), + ), + ) + return when (this) { + is Fee.Ethereum.EIP1559 -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + is Fee.Ethereum.Legacy -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + is Fee.Ethereum.TokenCurrency -> error("handle in [REDACTED_TASK_KEY]") + } + } + + private operator fun Fee.plus(otherFee: Fee): Fee { + if (this !is Fee.Ethereum || otherFee !is Fee.Ethereum) return this + val gasLimit = this.gasLimit + val increasedGasPrice = this.amount.value?.movePointRight(this.amount.decimals) + ?.divide(gasLimit.toBigDecimal(), RoundingMode.HALF_UP) + val increasedGasLimit = gasLimit + otherFee.gasLimit + val increasedAmount = this.amount.copy( + value = increasedGasLimit.toBigDecimal().multiply(increasedGasPrice).movePointLeft(this.amount.decimals), + ) + return when (this) { + is Fee.Ethereum.EIP1559 -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + is Fee.Ethereum.Legacy -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + is Fee.Ethereum.TokenCurrency -> this + } + } } \ No newline at end of file diff --git a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalUM.kt b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalUM.kt index 38f5698135..2b4429c482 100644 --- a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalUM.kt +++ b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/model/GiveApprovalUM.kt @@ -12,4 +12,5 @@ internal data class GiveApprovalUM( val isApproveButtonEnabled: Boolean, val isApproveLoading: Boolean, val isHoldToConfirm: Boolean = false, + val isResetApproval: Boolean = false, ) \ No newline at end of file diff --git a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/ui/GiveApprovalContent.kt b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/ui/GiveApprovalContent.kt index ce70ed4b1c..6b1fd64f72 100644 --- a/features/approval/impl/src/main/java/com/tangem/features/approval/impl/ui/GiveApprovalContent.kt +++ b/features/approval/impl/src/main/java/com/tangem/features/approval/impl/ui/GiveApprovalContent.kt @@ -27,28 +27,24 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.window.PopupProperties import com.tangem.common.ui.bottomsheet.permission.state.ApproveType +import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.approval.impl.model.GiveApprovalUM import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import com.tangem.common.ui.R as CommonUiR @Composable @Suppress("LongParameterList") internal fun GiveApprovalContent( currency: String, + uiState: GiveApprovalUM, subtitle: TextReference, - approveType: ApproveType, - approveItems: ImmutableList, onChangeApproveType: (ApproveType) -> Unit, - walletInteractionIcon: Int?, - isApproveEnabled: Boolean, - isApproveLoading: Boolean, - isHoldToConfirm: Boolean, onApproveClick: () -> Unit, onCancelClick: () -> Unit, onOpenLearnMoreAboutApproveClick: () -> Unit, @@ -73,20 +69,21 @@ internal fun GiveApprovalContent( ApprovalInfo( currency = currency, - approveType = approveType, - approveItems = approveItems, + approveType = uiState.approveType, + approveItems = uiState.approveItems, onChangeApproveType = onChangeApproveType, onOpenLearnMoreAboutApproveClick = onOpenLearnMoreAboutApproveClick, feeSelectorBlockComponent = feeSelectorBlockComponent, + isResetApproval = uiState.isResetApproval, ) SpacerH(height = TangemTheme.dimens.spacing20) - if (isHoldToConfirm) { + if (uiState.isHoldToConfirm) { HoldToConfirmButton( - text = stringResourceSafe(id = CommonUiR.string.common_approve), - enabled = isApproveEnabled, - isLoading = isApproveLoading, + text = stringResourceSafe(id = R.string.common_approve), + enabled = uiState.isApproveButtonEnabled, + isLoading = uiState.isApproveLoading, onConfirm = onApproveClick, modifier = Modifier .fillMaxWidth() @@ -94,21 +91,21 @@ internal fun GiveApprovalContent( ) } else { PrimaryButtonIconEnd( - text = stringResourceSafe(id = CommonUiR.string.common_approve), - iconResId = walletInteractionIcon, - showProgress = isApproveLoading, + text = stringResourceSafe(id = R.string.common_approve), + iconResId = uiState.walletInteractionIcon, + showProgress = uiState.isApproveLoading, modifier = Modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.spacing16), onClick = onApproveClick, - enabled = isApproveEnabled, + enabled = uiState.isApproveButtonEnabled, ) } SpacerH12() SecondaryButton( - text = stringResourceSafe(id = CommonUiR.string.common_cancel), + text = stringResourceSafe(id = R.string.common_cancel), modifier = Modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.spacing16), @@ -124,6 +121,7 @@ internal fun GiveApprovalContent( private fun ApprovalInfo( currency: String, approveType: ApproveType, + isResetApproval: Boolean, approveItems: ImmutableList, onChangeApproveType: (ApproveType) -> Unit, onOpenLearnMoreAboutApproveClick: () -> Unit, @@ -131,7 +129,7 @@ private fun ApprovalInfo( ) { FooterContainer( footer = annotatedReference { - append(stringResourceSafe(CommonUiR.string.swap_approve_description)) + append(stringResourceSafe(R.string.swap_approve_description)) append(" ") withLink( link = LinkAnnotation.Clickable( @@ -140,7 +138,7 @@ private fun ApprovalInfo( ), block = { appendColored( - text = stringResourceSafe(CommonUiR.string.common_learn_more), + text = stringResourceSafe(R.string.common_learn_more), color = TangemTheme.colors.text.accent, ) }, @@ -157,7 +155,13 @@ private fun ApprovalInfo( } SpacerH16() FooterContainer( - footer = resourceReference(CommonUiR.string.give_permission_policy_type_footer), + footer = resourceReference( + if (isResetApproval) { + R.string.update_approval_permission_fee_note + } else { + R.string.give_permission_policy_type_footer + }, + ), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { feeSelectorBlockComponent.Content( @@ -202,7 +206,7 @@ private fun AmountItem( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResourceSafe(id = CommonUiR.string.give_permission_rows_amount, currency), + text = stringResourceSafe(id = R.string.give_permission_rows_amount, currency), color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.subtitle1, maxLines = 1, @@ -215,7 +219,7 @@ private fun AmountItem( maxLines = 1, ) Icon( - painter = rememberVectorPainter(ImageVector.vectorResource(id = CommonUiR.drawable.ic_chevron_24)), + painter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.ic_chevron_24)), contentDescription = null, tint = TangemTheme.colors.icon.informative, modifier = Modifier.padding(start = TangemTheme.dimens.spacing2), @@ -275,10 +279,10 @@ private fun DropdownSelector( Text( text = when (item) { ApproveType.LIMITED -> stringResourceSafe( - id = CommonUiR.string.give_permission_current_transaction, + id = R.string.give_permission_current_transaction, ) ApproveType.UNLIMITED -> stringResourceSafe( - id = CommonUiR.string.give_permission_unlimited, + id = R.string.give_permission_unlimited, ) }, color = TangemTheme.colors.text.primary1, @@ -288,7 +292,7 @@ private fun DropdownSelector( SpacerWMax() Icon( painter = rememberVectorPainter( - image = ImageVector.vectorResource(id = CommonUiR.drawable.ic_check_24), + image = ImageVector.vectorResource(id = R.drawable.ic_check_24), ), tint = color, contentDescription = null, @@ -316,13 +320,8 @@ private fun GiveApprovalContentPreview( GiveApprovalContent( currency = params.currency, subtitle = params.subtitle, - approveType = params.approveType, - approveItems = params.approveItems, + uiState = params.uiState, onChangeApproveType = {}, - walletInteractionIcon = params.walletInteractionIcon, - isApproveEnabled = params.isApproveEnabled, - isApproveLoading = params.isApproveLoading, - isHoldToConfirm = false, onApproveClick = {}, onCancelClick = {}, onOpenLearnMoreAboutApproveClick = {}, @@ -334,11 +333,7 @@ private fun GiveApprovalContentPreview( private data class GiveApprovalPreviewParams( val currency: String, val subtitle: TextReference, - val approveType: ApproveType, - val approveItems: ImmutableList, - val walletInteractionIcon: Int?, - val isApproveEnabled: Boolean, - val isApproveLoading: Boolean, + val uiState: GiveApprovalUM, ) private class GiveApprovalContentPreviewProvider : PreviewParameterProvider { @@ -347,20 +342,36 @@ private class GiveApprovalContentPreviewProvider : PreviewParameterProvider Either)? abstract val onLoadFee: suspend () -> Either + abstract val onDisableCustomFee: () -> Boolean abstract val cryptoCurrencyStatus: CryptoCurrencyStatus abstract val feeCryptoCurrencyStatus: CryptoCurrencyStatus abstract val feeStateConfiguration: FeeStateConfiguration @@ -39,6 +40,7 @@ sealed class FeeSelectorParams { override val analyticsCategoryName: String, override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource, override val shouldShowOnlySpeedOption: Boolean = false, + override val onDisableCustomFee: () -> Boolean = { false }, val bottomSheetShown: (Boolean) -> Unit = {}, ) : FeeSelectorParams() @@ -56,6 +58,7 @@ sealed class FeeSelectorParams { override val analyticsCategoryName: String, override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource, override val shouldShowOnlySpeedOption: Boolean = false, + override val onDisableCustomFee: () -> Boolean = { false }, val callback: FeeSelectorModelCallback, ) : FeeSelectorParams() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt index cd1e7a333d..9cba99909c 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt @@ -45,6 +45,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor( params = FeeSelectorParams.FeeSelectorDetailsParams( state = model.uiState.value, onLoadFee = params.onLoadFee, + onDisableCustomFee = params.onDisableCustomFee, onLoadFeeExtended = params.onLoadFeeExtended, feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt index ac7220cf8e..5e0a7deecb 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt @@ -110,6 +110,7 @@ internal class FeeSelectorLogic @AssistedInject constructor( feeStateConfiguration = params.feeStateConfiguration, isFeeApproximate = isFeeApproximate(fee.transactionFee.normal.amount.type), feeSelectorIntents = this@FeeSelectorLogic, + shouldDisableCustomFee = params.onDisableCustomFee(), ), ) }, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeItemConverter.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeItemConverter.kt index 139d171f9a..a2c55aa0de 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeItemConverter.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeItemConverter.kt @@ -17,6 +17,7 @@ internal class FeeItemConverter( private val feeSelectorIntents: FeeSelectorIntents, private val appCurrency: AppCurrency, cryptoCurrencyStatus: CryptoCurrencyStatus, + private val shouldDisableCustomFee: Boolean, ) : Converter> { private val customFeeFieldConverter = FeeSelectorCustomFieldConverter( @@ -54,8 +55,10 @@ internal class FeeItemConverter( add(FeeItem.Market(fee = value.transactionFee.normal)) } } - val customFee = value.customFee ?: constructCustomFee() - customFee?.let(::add) + if (!shouldDisableCustomFee) { + val customFee = value.customFee ?: constructCustomFee() + customFee?.let(::add) + } } private fun MutableList.addFeeItemsLimited(value: Input) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt index 21fe42a28e..438b833d03 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/transformers/FeeSelectorLoadedTransformer.kt @@ -22,6 +22,7 @@ internal class FeeSelectorLoadedTransformer( private val feeStateConfiguration: FeeSelectorParams.FeeStateConfiguration, private val isFeeApproximate: Boolean, private val feeSelectorIntents: FeeSelectorIntents, + private val shouldDisableCustomFee: Boolean, ) : Transformer { private val feeItemsConverter = FeeItemConverter( @@ -30,6 +31,7 @@ internal class FeeSelectorLoadedTransformer( feeSelectorIntents = feeSelectorIntents, appCurrency = appCurrency, cryptoCurrencyStatus = feeCryptoCurrencyStatus, + shouldDisableCustomFee = shouldDisableCustomFee, ) override fun transform(prevState: FeeSelectorUM): FeeSelectorUM { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index 0dec39a031..61a3465420 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -78,6 +78,7 @@ internal class ShowApprovalBottomSheetTransformer( footerText = resourceReference(R.string.staking_give_permission_fee_footer), onChangeApproveType = prevState.clickIntents::onApproveTypeChange, onOpenLearnMoreAboutApproveClick = {}, + isResetApproval = false, ), walletInteractionIcon = walletInterationIcon(userWallet), onCancel = onDismiss, 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 7987443ea3..32031b0b71 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 @@ -10,7 +10,6 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionExtras -import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData @@ -2025,35 +2024,31 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } // setting up amount for approve with given amount for swap [SwapApproveType.Limited] - val callData = SmartContractCallDataProviderFactory.getApprovalCallData( - spenderAddress = requireNotNull(spenderAddress) { "spenderAddress cant be null" }, - amount = swapAmount.value.convertToSdkAmount(fromTokenStatus), - blockchain = fromToken.network.toBlockchain(), - ) - val feeData = try { - val extras = createTransactionExtrasUseCase( - callData = callData, - network = fromToken.network, - ).getOrNull() ?: error("unable to create extras") + val fromAddress = requireNotNull( + fromTokenStatus.value.networkAddress?.defaultAddress?.value, + ) { "networkAddress cant be null" } - val fromAddress = requireNotNull( - fromTokenStatus.value.networkAddress?.defaultAddress?.value, - ) { "networkAddress cant be null" } - val transactionData = TransactionData.Uncompiled( - amount = createNativeAmountForDex("0", fromToken.network), - destinationAddress = fromToken.getContractAddress(), - fee = null, - sourceAddress = fromAddress, - extras = extras, - ) - getFeeUseCase( - transactionData = transactionData, - network = fromToken.network, - userWallet = userWallet, - ).getOrNull() ?: error("unable to calculate fee") - } catch (e: Exception) { - TangemLogger.e("Failed to get fee", e) - // it's impossible next steps without fee + val allowanceInfo = getAllowanceInfoUseCase( + userWalletId = userWalletId, + cryptoCurrency = fromToken, + spenderAddress = requireNotNull(spenderAddress) { "spenderAddress cant be null" }, + requiredAmount = swapAmount.value, + ).getOrNull() + + val amount = if (allowanceInfo is AllowanceInfo.ResetNeeded) { + BigDecimal.ZERO + } else { + swapAmount.value + } + + val approveTransaction = createApprovalTransactionUseCase( + cryptoCurrencyStatus = fromTokenStatus, + userWalletId = userWalletId, + amount = amount, + contractAddress = fromToken.getContractAddress(), + spenderAddress = spenderAddress, + ).getOrElse { error -> + TangemLogger.e("Failed to create approveTransaction", error) return createSwapErrorWith( fromToken = fromTokenStatus, fromAccount = fromAccount, @@ -2063,6 +2058,12 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } + val feeData = getFeeUseCase( + transactionData = approveTransaction, + network = fromToken.network, + userWallet = userWallet, + ).getOrNull() ?: error("unable to calculate fee") + val feeState = feeData .patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_DEX) .toTxFeeState(fromToken, null) @@ -2084,6 +2085,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( amount = INFINITY_SYMBOL, walletAddress = getWalletAddress(fromToken.network), spenderAddress = getTokenAddress(fromToken), + isResetApproval = allowanceInfo is AllowanceInfo.ResetNeeded, requestApproveData = RequestApproveStateData( fee = feeState, fromTokenAmount = swapAmount, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index a630100e2e..89f79b2d72 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -87,6 +87,7 @@ sealed class PermissionDataState { val walletAddress: String, val spenderAddress: String, val requestApproveData: RequestApproveStateData, + val isResetApproval: Boolean, ) : PermissionDataState() object PermissionFailed : PermissionDataState() diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt index 7d480eb2cf..ed1fb1b425 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt @@ -206,10 +206,15 @@ internal class DefaultSwapComponent @AssistedInject constructor( feeCryptoCurrencyStatus = feeCryptoCurrency, amount = model.dataState.amount.orEmpty(), spenderAddress = requireNotNull(model.dataState.approveDataModel).spenderAddress, - subtitle = resourceReference( - id = R.string.give_permission_swap_subtitle, - formatArgs = wrappedList(providerName, permissionState.currency), - ), + subtitle = if (permissionState.isResetApproval) { + resourceReference(R.string.update_approval_permission_subtitle) + } else { + resourceReference( + id = R.string.give_permission_swap_subtitle, + formatArgs = wrappedList(providerName, permissionState.currency), + ) + }, + isResetApproval = permissionState.isResetApproval, isHoldToConfirm = model.isHoldToConfirmEnabled, callback = model.approvalCallback, ) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 1f11ab7fa1..ea5e0fe928 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -975,6 +975,7 @@ internal class StateBuilder( dialogText = resourceReference(R.string.swapping_approve_information_text), footerText = resourceReference(R.string.swap_give_permission_fee_footer), onOpenLearnMoreAboutApproveClick = onOpenLearnMoreAboutApproveClick, + isResetApproval = permissionDataState.isResetApproval, ) } }