Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-02 15:15:08 +05:00
parent a3b65cd194
commit d22bfd0c87
27 changed files with 342 additions and 139 deletions

View file

@ -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),

View file

@ -25,6 +25,7 @@ sealed class GiveTxPermissionState {
val approveItems: ImmutableList<ApproveType> = ApproveType.entries.toImmutableList(),
val approveButton: ApprovePermissionButton,
val cancelButton: CancelPermissionButton,
val isResetApproval: Boolean,
val onChangeApproveType: ((ApproveType) -> Unit)? = null,
val onOpenLearnMoreAboutApproveClick: () -> Unit,
) : GiveTxPermissionState()

View file

@ -1620,7 +1620,7 @@
<string name="tangempay_service_unavailable_title">Service vorübergehend nicht verfügbar</string>
<string name="tangempay_service_unreachable_try_later">Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin.</string>
<string name="tangempay_set_pin_code">Satz \nPIN-Code</string>
<string name="tangempay_sync_needed">Nicht synchronisiert</string>
<string name="tangempay_sync_needed">Sitzung abgelaufen</string>
<string name="tangempay_sync_needed_restore_access">Zugang wiederherstellen</string>
<string name="tangempay_tangem_visa_card">Nutzen Sie USDC für alltägliche Zahlungen</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay ist vorübergehend nicht erreichbar.</string>

View file

@ -1593,7 +1593,7 @@
<string name="tangempay_service_unavailable_title">Servicio temporalmente no disponible</string>
<string name="tangempay_service_unreachable_try_later">No es posible mostrar los datos en este momento, pero los pagos con tarjeta siguen funcionando.</string>
<string name="tangempay_set_pin_code">Establecer \nCódigo PIN</string>
<string name="tangempay_sync_needed">Sincronización necesaria</string>
<string name="tangempay_sync_needed">Sesión expirada</string>
<string name="tangempay_sync_needed_restore_access">Restablecer acceso</string>
<string name="tangempay_tangem_visa_card">Usa USDC para pagos cotidianos</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay temporalmente no disponible</string>

View file

@ -1590,7 +1590,7 @@
<string name="tangempay_service_unavailable_title">Service temporairement indisponible</string>
<string name="tangempay_service_unreachable_try_later">Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours.</string>
<string name="tangempay_set_pin_code">Définir le \ncode PIN</string>
<string name="tangempay_sync_needed">Synchronisation requise</string>
<string name="tangempay_sync_needed">Session expirée</string>
<string name="tangempay_sync_needed_restore_access">Restaurer l\'accès</string>
<string name="tangempay_tangem_visa_card">Utilisez USDC pour les paiements quotidiens</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay est temporairement indisponible</string>

View file

@ -177,7 +177,7 @@
<string name="tangempay_service_unavailable_description">Stiamo risolvendo un problema tecnico. Riprova più tardi.</string>
<string name="tangempay_service_unavailable_title">Servizio temporaneamente non disponibile</string>
<string name="tangempay_service_unreachable_try_later">Al momento non è possibile visualizzare i dati, ma i pagamenti con carta continuano a funzionare.</string>
<string name="tangempay_sync_needed">Sincronizzazione necessaria</string>
<string name="tangempay_sync_needed">Sessione scaduta</string>
<string name="tangempay_tangem_visa_card">Usa USDC per i pagamenti quotidiani</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay è temporaneamente non disponibile</string>
<string name="tangempay_title">Tangem Pay</string>

View file

@ -467,6 +467,19 @@
<string name="domain_receive_assets_onboarding_description">他のネットワークで資産を送金すると、永久に失われます。</string>
<string name="domain_receive_assets_onboarding_network_name">%sネットワーク</string>
<string name="domain_receive_assets_onboarding_title">下記のみを使用して資金を送金する</string>
<string name="dynamic_addresses">動的アドレス</string>
<string name="dynamic_addresses_custom_token_error_on_addition">動的アドレスが有効になっています。カスタムの「change」と「index」は利用できません。</string>
<string name="dynamic_addresses_enabled_toast_title">動的アドレスが有効です。</string>
<string name="dynamic_addresses_enter_features_privacy_description">取引ごとに新しいアドレスを使うことで、追跡されにくくなり、オンチェーン上のプライバシーが向上します。</string>
<string name="dynamic_addresses_enter_features_privacy_title">プライバシー強化</string>
<string name="dynamic_addresses_enter_features_receving_description">UTXOベースのネットワークでは、アドレスが自動生成されるため、手動で管理しなくても簡単に資金を受け取れます。</string>
<string name="dynamic_addresses_enter_features_receving_title">スムーズな受け取り</string>
<string name="dynamic_addresses_enter_main_button_title">動的アドレスを有効にする</string>
<string name="dynamic_addresses_enter_subtitle">動的アドレスでは、プライバシー強化のため毎回新しいアドレスが生成されますが、合計残高は変わりません。</string>
<string name="dynamic_addresses_error_has_custom_token_description">一部のカスタムアドレスまたはトークンで通常と異なる導出パスが使われているため、動的アドレスを有効にできません。</string>
<string name="dynamic_addresses_error_has_custom_token_title">動的アドレスは利用できません</string>
<string name="dynamic_addresses_error_service_unavailable_description">現在、プロバイダーに接続できません。しばらくしてからもう一度お試しください。</string>
<string name="dynamic_addresses_error_service_unavailable_title">サービスを利用できません。しばらくしてからもう一度お試しください。</string>
<string name="earn_best_opportunities">おすすめ</string>
<string name="earn_clear_filter">絞り込みを解除</string>
<string name="earn_empty">リストは現在更新中のため、一時的に空になっています。しばらくしてからご確認ください。</string>
@ -745,7 +758,10 @@
<string name="markets_loading_no_data_title">データなし</string>
<string name="markets_pulse_common_title">マーケット動向</string>
<string name="markets_quick_actions">クイックアクション</string>
<string name="markets_search_clear_all_hints">すべてクリア</string>
<string name="markets_search_header_title">トークンを探す</string>
<string name="markets_search_hint_header">最近</string>
<string name="markets_search_portfolio_header">ポートフォリオ内</string>
<string name="markets_search_result_title">結果</string>
<string name="markets_search_see_tokens_under_100k">時価総額10万ドル以下のトークンを見る</string>
<string name="markets_search_show_tokens">トークンを表示</string>
@ -820,9 +836,11 @@
<string name="markets_token_details_market_rating_description">時価総額に基づくすべてのコイン間の暗号資産評価における位置</string>
<string name="markets_token_details_market_rating_full">市場格付け</string>
<string name="markets_token_details_max_supply">最大供給量</string>
<string name="markets_token_details_max_supply_and_circulation_full">流通量と最大供給量</string>
<string name="markets_token_details_max_supply_description">特定の暗号資産に存在しうるコインまたはトークンの最大数</string>
<string name="markets_token_details_max_supply_full">最大供給量</string>
<string name="markets_token_details_metrics">指標</string>
<string name="markets_token_details_metrics_no_limited">制限なし</string>
<string name="markets_token_details_official_links">公式リンク</string>
<string name="markets_token_details_price_performance">値動き</string>
<string name="markets_token_details_repository">リポジトリ</string>
@ -1095,6 +1113,8 @@
<string name="qr_scanner_error_unrecognized_title">認識できないQRコード</string>
<string name="qr_scanner_error_unsupported_network_message">このネットワークは、追加されているトークンのいずれにも対応していません。対応しているトークンを追加してから暗号資産を送金してください。</string>
<string name="qr_scanner_error_unsupported_network_title">対応しているトークンが見つかりません</string>
<string name="qr_scanner_warning_unknown_parameters_message">このQRコードには認識できないパラメータが含まれています%s。続行すると、一部の支払い情報が失われる可能性があります。</string>
<string name="qr_scanner_warning_unknown_parameters_title">不明なパラメータ</string>
<string name="receive_bottom_sheet_no_memo_required_message">メモ不要</string>
<string name="receive_bottom_sheet_warning_message">%3$sネットワーク上の%1$s ( %2$s )</string>
<string name="receive_bottom_sheet_warning_message_compact">%2$sネットワーク上の%1$s</string>
@ -1477,6 +1497,7 @@
<string name="swapping_fee_estimation_error_text">手数料見積りエラーです。サポートにフィードバックをお送りください。</string>
<string name="swapping_from_title">スワップする</string>
<string name="swapping_high_price_impact_description">選択したトークンをこの量を交換すると、価格に大きな影響が生じ、結果が減少します。</string>
<string name="swapping_high_price_impact_text">流動性が低いため、受取額が大幅に少なくなる可能性があります。金額を減らすか、別のプロバイダーをお試しください。</string>
<string name="swapping_high_price_impact_title">価格への影響が甚大です</string>
<string name="swapping_insufficient_funds">残高不足</string>
<string name="swapping_insufficient_funds_description">この取引を完了するには残高が不足しています。受け取り額を減らすか、資金を追加してください。</string>
@ -1486,6 +1507,8 @@
<string name="swapping_to_title">受け取る</string>
<string name="swapping_token_list_title">トークンを選択</string>
<string name="swapping_token_not_available">利用不可</string>
<string name="swapping_trade_too_large_text">この取引に十分な流動性がありません。\n金額を減らすか、別のプロバイダーを選択してください。</string>
<string name="swapping_trade_too_large_title">取引額が大きすぎます</string>
<string name="tangem_pay_beta_notification_subtitle">皆様からのフィードバックをお待ちしております</string>
<string name="tangem_pay_beta_notification_title">Tangem Payのベータ版を公開しました</string>
<string name="tangem_pay_card_frozen">カードが凍結されています</string>
@ -1609,7 +1632,7 @@
<string name="tangempay_service_unavailable_title">サービスは一時的に利用できません</string>
<string name="tangempay_service_unreachable_try_later">現在、データを表示できませんが、カードでのお支払いは引き続きご利用いただけます。</string>
<string name="tangempay_set_pin_code">\nPINコードの設定</string>
<string name="tangempay_sync_needed">未同期</string>
<string name="tangempay_sync_needed">セッションの有効期限が切れました</string>
<string name="tangempay_sync_needed_restore_access">アクセスを復元</string>
<string name="tangempay_tangem_visa_card">日常の支払いにUSDCを利用</string>
<string name="tangempay_temporarily_unavailable">Tangem Payは現在一時的に利用できません。</string>
@ -1646,6 +1669,7 @@
<string name="token_details_token_type_subtitle_no_standard">%%image%% %1$sネットワーク上のトークン</string>
<string name="token_details_unable_hide_alert_message">%1$s ( %2$s ) トークンは%3$sネットワークの主要通貨であり、このネットワーク上の他のトークンがリストにある限り、非表示にすることはできません。</string>
<string name="token_details_unable_hide_alert_title">%sを非表示にできません</string>
<string name="token_market_metrics_no_data">該当なし</string>
<string name="token_receive_show_qr_code_title">QRコードを表示</string>
<string name="token_swap_changelly_promotion_message">このトークンは、2月%2$s-%3$s の間、%1$s のサービス手数料で別のトークンと交換できます。</string>
<string name="token_swap_changelly_promotion_title">Changellyでスワップ、手数料%s</string>

View file

@ -1631,7 +1631,7 @@
<string name="tangempay_service_unavailable_title">Serviço temporariamente indisponível</string>
<string name="tangempay_service_unreachable_try_later">Não foi possível exibir os detalhes. No entanto, os pagamentos com cartão ainda estão funcionando.</string>
<string name="tangempay_set_pin_code">Defina o código PIN.</string>
<string name="tangempay_sync_needed">Não sincronizado</string>
<string name="tangempay_sync_needed">Sessão expirada</string>
<string name="tangempay_sync_needed_restore_access">Restaurar acesso</string>
<string name="tangempay_tangem_visa_card">Use USDC para pagamentos do dia a dia.</string>
<string name="tangempay_temporarily_unavailable">O serviço Tangem Pay está temporariamente inacessível.</string>

View file

@ -1624,7 +1624,7 @@
<string name="tangempay_service_unavailable_description">Мы устраняем техническую проблему. Пожалуйста, попробуйте позже.</string>
<string name="tangempay_service_unavailable_title">Сервис временно недоступен</string>
<string name="tangempay_service_unreachable_try_later">Не можем показать данные карты, но оплаты продолжают работать.</string>
<string name="tangempay_sync_needed">Не синхронизирован</string>
<string name="tangempay_sync_needed">Сессия истекла</string>
<string name="tangempay_tangem_visa_card">Оплачивайте ежедневные покупки в USDC</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay временно недоступен</string>
<string name="tangempay_title">Tangem Pay</string>

View file

@ -1633,7 +1633,7 @@
<string name="tangempay_service_unavailable_title">Сервіс тимчасово недоступний</string>
<string name="tangempay_service_unreachable_try_later">Не можемо показати дані картки, але оплати продовжують працювати.</string>
<string name="tangempay_set_pin_code">Встановіть \nPIN-код</string>
<string name="tangempay_sync_needed">Не синхронізовано</string>
<string name="tangempay_sync_needed">Сесія закінчилася</string>
<string name="tangempay_sync_needed_restore_access">Відновити доступ</string>
<string name="tangempay_tangem_visa_card">Використовуйте USDC для щоденних платежів</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay тимчасово недоступний</string>

View file

@ -419,7 +419,7 @@
<string name="tangempay_service_unavailable_description">我们正在修复技术问题。请稍后再试。</string>
<string name="tangempay_service_unavailable_title">服務暫時無法使用</string>
<string name="tangempay_service_unreachable_try_later">目前無法顯示資料,但卡片支付仍可正常使用。</string>
<string name="tangempay_sync_needed">需要同步</string>
<string name="tangempay_sync_needed">工作階段已過期</string>
<string name="tangempay_tangem_visa_card">使用 USDC 進行日常支付</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay暂时不可用</string>
<string name="tangempay_title">Tangem Pay</string>

View file

@ -422,6 +422,7 @@
<string name="custom_token_creation_error_token_already_exist_title">Token already exists</string>
<string name="custom_token_creation_error_wrong_decimals">Decimal must be a valid integer, up to %d</string>
<string name="custom_token_custom_derivation">Custom derivation</string>
<string name="custom_token_custom_derivation_dynamic_addresses_enabled_error">Dynamic Addresses is enabled. Custom \\"change\\" and \\"index\\" are unavailable.</string>
<string name="custom_token_custom_derivation_placeholder">E. g. m/00\'/0000\'/0\'/0/0</string>
<string name="custom_token_custom_derivation_title">Enter custom derivation</string>
<string name="custom_token_decimals_input_title">Decimals</string>
@ -475,6 +476,7 @@
<string name="domain_receive_assets_onboarding_network_name">%s network</string>
<string name="domain_receive_assets_onboarding_title">Send funds using only</string>
<string name="dynamic_addresses">Dynamic addresses</string>
<string name="dynamic_addresses_custom_token_error_on_addition">Dynamic Addresses is enabled. Custom \"change\" and \"index\" are unavailable.</string>
<string name="dynamic_addresses_enabled_toast_title">Dynamic addresses enabled</string>
<string name="dynamic_addresses_enter_features_privacy_description">Use a new address for each transaction to reduce traceability and improve on-chain privacy.</string>
<string name="dynamic_addresses_enter_features_privacy_title">Enhanced Privacy</string>
@ -1652,7 +1654,7 @@
<string name="tangempay_service_unavailable_title">Service temporarily unavailable</string>
<string name="tangempay_service_unreachable_try_later">Unable to display details. However, card payments are still working.</string>
<string name="tangempay_set_pin_code">Set \nPIN code</string>
<string name="tangempay_sync_needed">Not synced</string>
<string name="tangempay_sync_needed">Session expired</string>
<string name="tangempay_sync_needed_restore_access">Restore access</string>
<string name="tangempay_tangem_visa_card">Use USDC for everyday payments</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay is temporarily unreachable</string>
@ -1732,6 +1734,9 @@
<string name="universal_error">We\'ve encountered an error. Error code: %s. Please contact our support.</string>
<string name="unlock_wallet_description_full">Use %s or scan a card/ring to have access to your wallet</string>
<string name="unsupported_wc_version">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.</string>
<string name="update_approval_permission_fee_note">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.</string>
<string name="update_approval_permission_subtitle">Transaction exceeds previously granted permission amount.\nUpdate permission to proceed</string>
<string name="update_approval_permission_title">Update permission</string>
<string name="upgrade_to_hardware_wallet_button_title">Upgrade to hardware wallet</string>
<string name="user_push_notification_agreement_argument_one">Stay up to date with the latest features and news</string>
<string name="user_push_notification_agreement_argument_one_subtitle">Real-time alerts for transactions, exchanges, and critical updates.</string>

View file

@ -16,6 +16,7 @@ interface GiveApprovalComponent : ComposableBottomSheetComponent {
val spenderAddress: String,
val subtitle: TextReference,
val isHoldToConfirm: Boolean = false,
val isResetApproval: Boolean = false,
val callback: Callback,
)

View file

@ -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<TangemBottomSheetConfigContent.Empty>(
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,

View file

@ -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<TransactionData.Uncompiled, TransactionFee> = 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<Throwable, TransactionData> {
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<GetFeeError, TransactionFee> {
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<GetFeeError, TransactionFee> {
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<GetFeeError, TransactionFeeExtended> {
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 <T> onApprovalTx(
onApprove: suspend (TransactionData.Uncompiled) -> Either<GetFeeError, T>,
onResetApprove:
suspend (Pair<TransactionData.Uncompiled, TransactionData.Uncompiled>) -> Either<GetFeeError, T>,
): Either<GetFeeError, T> {
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
}
}
}

View file

@ -12,4 +12,5 @@ internal data class GiveApprovalUM(
val isApproveButtonEnabled: Boolean,
val isApproveLoading: Boolean,
val isHoldToConfirm: Boolean = false,
val isResetApproval: Boolean = false,
)

View file

@ -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<ApproveType>,
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<ApproveType>,
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<ApproveType>,
val walletInteractionIcon: Int?,
val isApproveEnabled: Boolean,
val isApproveLoading: Boolean,
val uiState: GiveApprovalUM,
)
private class GiveApprovalContentPreviewProvider : PreviewParameterProvider<GiveApprovalPreviewParams> {
@ -347,20 +342,36 @@ private class GiveApprovalContentPreviewProvider : PreviewParameterProvider<Give
GiveApprovalPreviewParams(
currency = "USDT",
subtitle = stringReference("Allow this app to access your USDT"),
approveType = ApproveType.LIMITED,
approveItems = persistentListOf(ApproveType.LIMITED, ApproveType.UNLIMITED),
walletInteractionIcon = CommonUiR.drawable.ic_tangem_24,
isApproveEnabled = true,
isApproveLoading = false,
uiState = GiveApprovalUM(
approveType = ApproveType.LIMITED,
approveItems = persistentListOf(ApproveType.LIMITED, ApproveType.UNLIMITED),
walletInteractionIcon = R.drawable.ic_tangem_24,
isApproveButtonEnabled = true,
isApproveLoading = false,
),
),
GiveApprovalPreviewParams(
currency = "USDC",
subtitle = stringReference("Allow this app to access your USDC"),
approveType = ApproveType.UNLIMITED,
approveItems = persistentListOf(ApproveType.LIMITED, ApproveType.UNLIMITED),
walletInteractionIcon = CommonUiR.drawable.ic_tangem_24,
isApproveEnabled = false,
isApproveLoading = true,
uiState = GiveApprovalUM(
approveType = ApproveType.UNLIMITED,
approveItems = persistentListOf(ApproveType.LIMITED, ApproveType.UNLIMITED),
walletInteractionIcon = R.drawable.ic_tangem_24,
isApproveButtonEnabled = false,
isApproveLoading = true,
),
),
GiveApprovalPreviewParams(
currency = "USDC",
subtitle = stringReference("Allow this app to access your USDC"),
uiState = GiveApprovalUM(
approveType = ApproveType.UNLIMITED,
approveItems = persistentListOf(ApproveType.LIMITED, ApproveType.UNLIMITED),
walletInteractionIcon = R.drawable.ic_tangem_24,
isApproveButtonEnabled = true,
isApproveLoading = false,
isResetApproval = true,
),
),
)
}

View file

@ -17,6 +17,7 @@ sealed class FeeSelectorParams {
abstract val userWalletId: UserWalletId
abstract val onLoadFeeExtended: (suspend (CryptoCurrencyStatus?) -> Either<GetFeeError, TransactionFeeExtended>)?
abstract val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>
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()

View file

@ -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,

View file

@ -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(),
),
)
},

View file

@ -17,6 +17,7 @@ internal class FeeItemConverter(
private val feeSelectorIntents: FeeSelectorIntents,
private val appCurrency: AppCurrency,
cryptoCurrencyStatus: CryptoCurrencyStatus,
private val shouldDisableCustomFee: Boolean,
) : Converter<FeeItemConverter.Input, ImmutableList<FeeItem>> {
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<FeeItem>.addFeeItemsLimited(value: Input) {

View file

@ -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<FeeSelectorUM> {
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 {

View file

@ -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,

View file

@ -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,

View file

@ -87,6 +87,7 @@ sealed class PermissionDataState {
val walletAddress: String,
val spenderAddress: String,
val requestApproveData: RequestApproveStateData,
val isResetApproval: Boolean,
) : PermissionDataState()
object PermissionFailed : PermissionDataState()

View file

@ -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,
)

View file

@ -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,
)
}
}