Skip to content

Improve fees display on send#2649

Open
leofelix077 wants to merge 26 commits intomasterfrom
feature/improve-resource-fee-display-on-send
Open

Improve fees display on send#2649
leofelix077 wants to merge 26 commits intomasterfrom
feature/improve-resource-fee-display-on-send

Conversation

@leofelix077
Copy link
Copy Markdown
Collaborator

@leofelix077 leofelix077 commented Mar 12, 2026

closes #2475

Screen.Recording.2026-03-13.at.11.42.36.mov
Screen.Recording.2026-03-13.at.11.43.27.mov

@leofelix077 leofelix077 self-assigned this Mar 12, 2026
@leofelix077 leofelix077 added wip not for merging yet don't review yet wip / tests in progress / missing videos or screenshots / pending self code-review labels Mar 12, 2026
Copilot AI review requested due to automatic review settings March 12, 2026 20:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the resource fee display on the send flow by adding a detailed fee breakdown pane to the review transaction screen, showing inclusion fee and resource fee separately for Soroban transactions.

Changes:

  • Added a new "Fee breakdown" pane in ReviewTransaction showing inclusion fee, resource fee, and total fee with contextual descriptions for classic vs Soroban transactions
  • Propagated inclusionFee and resourceFee from simulation results through both send and collectible flows
  • Added "Calculating..." loading state for fees and a loading indicator on the continue button during simulation

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
extension/src/popup/components/InternalTransaction/ReviewTransaction/index.tsx New fees breakdown pane with fee detail rows and info button
extension/src/popup/components/InternalTransaction/ReviewTransaction/styles.scss Styles for the fees breakdown pane and info button
extension/src/popup/components/send/SendAmount/index.tsx Show "Calculating..." during simulation, loading state on button
extension/src/popup/components/send/SendAmount/hooks/useSimulateTxData.tsx Pass inclusionFee and resourceFee from simulation
extension/src/popup/components/sendCollectible/SelectedCollectible/hooks/useSimulateTxData.ts Pass inclusionFee and resourceFee from simulation
extension/src/popup/locales/en/translation.json New i18n keys for fee breakdown UI
extension/src/popup/locales/pt/translation.json Portuguese translations for new keys (with one regression)
@shared/api/internal.ts Added network_url to simulate-tx request body
extension/e2e-tests/reviewTxFees.test.ts E2E tests for fee breakdown in token, classic, and collectible sends

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@leofelix077 leofelix077 changed the title Feature/improve resource fee display on send Improve resource fee display on send Mar 13, 2026
….com:stellar/freighter into feature/improve-resource-fee-display-on-send
@leofelix077 leofelix077 changed the title Improve resource fee display on send Improve fees display on send Mar 13, 2026
@leofelix077 leofelix077 requested a review from Copilot March 13, 2026 15:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@leofelix077 leofelix077 added enhancement New feature or request and removed wip not for merging yet don't review yet wip / tests in progress / missing videos or screenshots / pending self code-review labels Mar 13, 2026
@sdfcharles
Copy link
Copy Markdown

sdfcharles commented Mar 13, 2026

@leofelix077 looks nice! few notes:

CleanShot 2026-03-13 at 11 06 29
  1. should this be Inclusion fee?
CleanShot 2026-03-13 at 11 07 24
  1. lets match the top/bottom padding here
CleanShot 2026-03-13 at 13 18 43
  1. lets move the copy button next to the text
CleanShot 2026-03-13 at 13 20 47
  1. i like the idea of the buttons here but lets drop them for now for consistency

leofelix077 and others added 3 commits March 16, 2026 09:30
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@leofelix077
Copy link
Copy Markdown
Collaborator Author

@sdfcharles applied the adjustments to the UI parts now. thanks for checking!

Screenshot 2026-03-16 at 10 40 19 Screenshot 2026-03-16 at 10 40 23 Screenshot 2026-03-16 at 10 40 30

leofelix077 and others added 3 commits March 17, 2026 19:56
- Extract SimulateTxData interface to types/transactions.ts to remove
  duplicate definitions in send and collectible hooks
- Both hooks now re-export the shared type
- FeesPane and ReviewTransaction import directly from types/transactions
- Fix FeesPane top/bottom padding and reset padding inside multi-pane-slider

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
body: JSON.stringify({
xdr,
network_passphrase: networkDetails.networkPassphrase,
network_url: networkDetails.sorobanRpcUrl,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this change needed? It's actually only used for Soroswap, which is disabled right now

Copy link
Copy Markdown
Collaborator Author

@leofelix077 leofelix077 Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was throwing an error when trying to submit a collectible, mentioning it's a required field by the backend. on my MP collectibles

</>
) : null}
{isEditingSettings ? (
{isEditingSettings && !isShowingFeesPane ? (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 small UX improvement we can make here. I noticed if you edit the fee, then click the inclusion fee i bubble, it resets the fee field. A user would lose any change they've made in this field

Screen.Recording.2026-03-25.at.12.01.33.PM.mov

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice one!

I adjusted the fee persistence inside the flow, so if user manually edits it, it's kept inside the context (soroban vs classic) and won't change again after explicit user input, unless user exits the transaction flow

Screen.Recording.2026-04-02.at.13.47.14.mov

Copy link
Copy Markdown
Contributor

PR Review: Improve fees display on send (Extension)

Cross-reference: This review covers this PR alongside the mobile counterpart at stellar/freighter-mobile#774. A comparison section is included at the bottom.


Individual Review of Extension PR

Overall: Clean, well-structured implementation. The FeesPane component is simple and reusable, the pane state machine extension is elegant, and the E2E test coverage is excellent.

Things I Like

  • Comprehensive E2E tests (358 lines) covering token send, classic send, collectible send, EditSettings flow, auto-simulation, and re-simulation with destination change. This is significantly stronger test coverage than the mobile PR.
  • SimulateTxData consolidated into types/transactions.ts — removes duplicate interface definitions from two separate hooks.
  • lastInclusionFeeRef pattern — persists the inclusion fee across re-simulations so the EditSettings input doesn't flash back to the total fee. Well thought out.
  • Base fee reset before re-simulation in both handleContinue and the auto-simulate useEffect — prevents the compounding fee inflation bug where total gets used as baseFee.
  • simulationDataRef for change detection — clean approach to detect destination/asset changes without watching simulationState.data (which would cause loops).
  • FeesPane takes simulationState directly — it can determine Soroban vs classic from the data itself (resourceFee presence), which is simpler than the mobile's dual isSoroban/isSorobanContext approach.
  • enableReinitialize on Formik — ensures EditSettings reflects updated fee values on re-open.

Issues & Suggestions

1. Auto-simulation useEffect depends on simulationState.state ⚠️

useEffect(() => {
  if (simulationState.state === RequestState.LOADING) return;
  // ...
  if (destChanged || assetChanged) {
    fetchSimulationData();
  }
}, [destination, asset, isToken, isCollectible, simulationState.state]);

Including simulationState.state in deps means this effect re-runs on every state transition (IDLE → LOADING → SUCCESS). The simulationDataRef guard prevents duplicate calls in the happy path, but if a simulation fails (ERROR state), the effect re-runs, sees the same destination/asset (already tracked in the ref), and does nothing — the user is stuck without a retry mechanism. Consider adding ERROR handling or a manual retry path.

2. FeesPane determines Soroban vs classic based on simulationState.data?.resourceFee 📝

{simulationState.data?.resourceFee
  ? t("Fees description soroban")
  : t("Fees description classic")}

This works for the fee breakdown description, but has a subtle issue: while loading (data is null), the description shows the classic text even for a Soroban transaction. For the mobile PR, an explicit isSorobanContext prop solves this. Consider passing a similar hint so the description is correct during loading states too.

3. FeesPaneOverlay style class is defined but unused 🧹
In styles.scss, the .FeesPaneOverlay class is defined (13 lines) but never referenced in any component. Looks like a leftover from an earlier approach — can be removed.

4. Info button always shown on ReviewTransaction fee row 📝

<button
  className="ReviewTx__Details__Row__FeesInfoBtn"
  data-testid="review-tx-fee-info-btn"
  onClick={() => setActivePaneIndex(paneConfig.feesIndex)}
>
  <Icon.InfoCircle />
</button>

The info circle button is always rendered (both Soroban and classic). This is fine since the FeesPane adapts its content, but it's a design choice worth confirming — the mobile PR only shows the info icon for Soroban transactions on the review screen.

5. network_url added to simulate-tx API call 📝

body: JSON.stringify({
  xdr,
  network_passphrase: networkDetails.networkPassphrase,
  network_url: networkDetails.sorobanRpcUrl,
}),

Small but important change in @shared/api/internal.ts. Worth noting this is a backend contract change — if the backend doesn't expect network_url, it should be harmless (ignored), but worth confirming the backend accepts this field.

6. Fee display shows "Calculating..." only for Soroban
Good UX — classic transactions don't need this since they don't require simulation. The isLoading check in the FeesPane also correctly treats IDLE as loading.


Cross-Platform Comparison: Extension (#2649) vs Mobile (#774)

Both PRs implement the same feature (Soroban fee breakdown: Inclusion Fee + Resource Fee + Total Fee) across the two Freighter platforms. Here's how they differ:

Aspect Extension (#2649) Mobile (#774)
State management Redux + useRequest pattern (simulationState with RequestState enum) Zustand store (useTransactionBuilderStore) with isSoroban, sorobanResourceFeeXlm, sorobanInclusionFeeXlm
Fee data location SimulateTxData interface (shared type) carries inclusionFee? and resourceFee? Stored directly in Zustand store; computed in components via BigNumber
Soroban detection (FeesPane) Inferred from simulationState.data?.resourceFee presence Dual system: isSoroban from store + isSorobanContext prop from parent context
Fee breakdown component FeesPane — SCSS-styled, integrated into MultiPaneSlider FeeBreakdownBottomSheet — NativeWind/Tailwind, uses @gorhom/bottom-sheet
Auto-simulation trigger useEffect watching destination + asset with simulationDataRef for dedup useEffect with lastAutoSimulatedKey ref; uses amount "0" for early estimation
Fee estimation approach Simulates with the real amount; auto-triggers when destination changes Simulates with amount "0" to get fees before user enters amount; skipAmountValidation flag
Base fee inflation prevention lastInclusionFeeRef + explicit saveTransactionFee reset before re-simulation Inclusion fee derived from params.transactionFee || MIN_TRANSACTION_FEE in the store
Total fee computation Computed in simulateTxrecommendedFee = baseFee + minResourceFee; single source of truth Computed independently in both FeeBreakdownBottomSheet and SendReviewBottomSheet (duplicated)
EditSettings fee label "Inclusion Fee" for Soroban, "Transaction Fee" for classic; info button opens FeesPane "Inclusion fee" for Soroban, "Transaction fee" for classic; info button opens FeeBreakdownBottomSheet
Info icon on review screen Always shown (classic and Soroban) Only shown for Soroban transactions
Simulation return type Unchanged — useSimulateTxData hook returns data via existing pattern Breaking change: simulateContractTransfer and simulateCollectibleTransfer now return { preparedTransaction, minResourceFee } instead of raw XDR string
Error handling Existing patterns New extractErrorMessage helper for plain-object API errors
Test coverage 358-line E2E test suite (6 test cases including re-simulation) Unit tests updated for store + service changes; no E2E tests
i18n EN + PT translations EN + PT translations (matching keys/descriptions)
Loading state "Calculating..." text in FeesPane and fee display ActivityIndicator spinner in bottom sheet and fee row

Key Architectural Differences Worth Aligning

  1. Total fee computation: The extension computes the total fee in one place (recommendedFee in the simulation hook). The mobile computes it in two components independently. The extension approach is DRY-er — consider centralizing this in the mobile's Zustand store.

  2. Fee estimation with amount 0: The mobile PR introduces a feeEstimationAmount parameter and skipAmountValidation to simulate with amount 0 before the user enters a value. The extension doesn't do this — it only simulates when the destination changes (using the real amount). This is a meaningful UX difference: the mobile user sees estimated fees earlier, but the extension approach avoids the complexity of dummy builds.

  3. Soroban detection strategy: The extension's FeesPane infers Soroban from the data (resourceFee presence), which is simpler but shows classic text during loading. The mobile uses an explicit isSorobanContext prop, which is more correct during loading but adds complexity. Consider aligning on one approach.

  4. Breaking vs non-breaking simulation API: The mobile changes simulateContractTransfer's return type (breaking), while the extension adds inclusionFee/resourceFee to the existing SimulateTxData type (non-breaking). The extension approach is safer.

  5. Review screen info icon visibility: The extension always shows the fee info icon; the mobile only shows it for Soroban. Worth deciding on a consistent approach across platforms.

  6. Unrelated change in mobile PR: The cancel button logic change in SendReviewFooter (tertiary/destructive conditions) doesn't appear in the extension PR and seems unrelated to fee breakdown.


Generated by Claude Code

leofelix077 and others added 13 commits April 1, 2026 09:13
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Extract SimulateTxData interface to types/transactions.ts to remove
  duplicate definitions in send and collectible hooks
- Both hooks now re-export the shared type
- FeesPane and ReviewTransaction import directly from types/transactions
- Fix FeesPane top/bottom padding and reset padding inside multi-pane-slider

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@leofelix077
Copy link
Copy Markdown
Collaborator Author

@CassioMG @piyalbasu I adjusted the last points on the code and checked the Claude comments

most of the comments are related to data source and state management, which are inherently different on mobile vs extension, and some of the UI parts that are also different on Mobile (e.g. on mobile we have the info sheet for the transaction fee, on extension we always show the fees breakdown)

Funcionality-wise they should be in parity now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve minResourceFee display in Send flow

5 participants