Skip to content

Add create stream fee preview#554

Merged
ritik4ever merged 1 commit into
ritik4ever:mainfrom
utahkanz-ops:feat/460-create-stream-fee-preview
Jun 1, 2026
Merged

Add create stream fee preview#554
ritik4ever merged 1 commit into
ritik4ever:mainfrom
utahkanz-ops:feat/460-create-stream-fee-preview

Conversation

@utahkanz-ops
Copy link
Copy Markdown
Contributor

@utahkanz-ops utahkanz-ops commented May 31, 2026

Summary

  • Adds a create-stream fee preview flow so submitting the form first estimates the Soroban network fee and opens a confirmation modal.
  • The preview shows the total amount, stream rate, and network fee estimate in XLM before the user confirms stream creation.
  • Simulation errors are shown inline and the existing create flow only runs after Confirm.

Issue

Changes

  • Added a backend /api/streams/fee-estimate route that validates the create stream payload and calls Soroban RPC simulateTransaction through the existing stream store context.
  • Refactored stream transaction construction so creation and fee simulation use the same create_stream operation shape.
  • Added a frontend API helper for stream fee estimation.
  • Updated CreateStreamForm to estimate on submit, show a confirmation modal, support Cancel/Confirm, and preserve the form when simulation fails.
  • Added Vitest coverage that mocks the fee simulation and asserts the fee preview before confirmation.

Validation

  • Ran npm.cmd --prefix frontend test -- --run src/components/CreateStreamForm.test.tsx - passed, 1 file / 8 tests.
  • Ran npm.cmd --prefix frontend run build - failed before this change's code due existing syntax errors in frontend/src/components/IssueBacklog.tsx, IssueBacklog.test.tsx, StreamsTable.tsx, and StreamsTable.test.tsx.
  • Ran npm.cmd --prefix backend run build - failed due existing syntax errors in backend/src/index.test.ts, backend/src/index.ts, and backend/src/services/streamStore.ts.
  • Ran npm.cmd --prefix backend test -- --run src/index.test.ts - failed because backend/src/index.test.ts has a pre-existing parse error at line 416.

Notes

  • The original PR opened against ritik4ever/stellar-bounty-board was for the wrong repository because the provided issue number belongs to ritik4ever/stellar-stream.

Summary by CodeRabbit

Release Notes

  • New Features
    • Stream creation now includes fee estimation before confirmation
    • A confirmation modal displays the estimated network fee, total stream amount, and stream rate details before finalizing creation
    • Submit button displays "Estimating fee..." status while calculating fees

@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@utahkanz-ops is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 31, 2026

@utahkanz-ops Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements stream creation fee estimation with user confirmation. The backend adds Soroban transaction simulation infrastructure and exposes a fee-estimate endpoint; the frontend form calls this endpoint on submit, displays fees in a confirmation modal, and requires user approval before proceeding with stream creation. Tests cover the new simulation and confirmation flows.

Changes

Stream Creation Fee Estimation

Layer / File(s) Summary
Backend fee estimation infrastructure
backend/src/services/streamStore.ts, backend/src/index.ts
StreamFeeEstimate type and shared createStreamOperation helper enable transaction simulation; estimateCreateStreamFee simulates stream creation via RPC and returns stroops + XLM fees. POST /api/streams/fee-estimate endpoint validates request and returns the estimate.
Frontend API bridge
frontend/src/services/api.ts
estimateCreateStreamFee makes authenticated POST to fee-estimate endpoint and returns StreamFeeEstimate.
Form submission and fee confirmation
frontend/src/components/CreateStreamForm.tsx
Form builds CreateStreamPayload on submit, calls estimateCreateStreamFee, displays confirmation modal with amount/rate/fee details, and calls onCreate only after user confirms. Submit button shows "Estimating fee..." during simulation and simulation errors display inline.
Fee preview styling
frontend/src/index.css
Grid and row layout rules for fee preview list with borders, rounded corners, and right-aligned bold descriptions.
Form tests for fee estimation
frontend/src/components/CreateStreamForm.test.tsx
Updated test setup mocks fee estimation; new test verifies fee simulation shows preview and requires confirmation; new test asserts error handling; updated validation and loading-state tests cover confirmation modal behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Form as CreateStreamForm
  participant API as estimateCreateStreamFee
  participant Backend as POST /api/streams/fee-estimate
  participant RPC as Soroban RPC
  User->>Form: submit form with stream details
  Form->>Form: build CreateStreamPayload
  Form->>API: estimateCreateStreamFee(payload)
  API->>Backend: POST with payload + auth
  Backend->>RPC: simulateTransaction(create_stream)
  RPC-->>Backend: simulation result with fee
  Backend-->>API: { data: { feeStroops, feeXlm } }
  API-->>Form: StreamFeeEstimate
  Form->>Form: setFeePreview(payload, estimate)
  Form->>Form: render confirmation modal
  User->>Form: click Confirm or Cancel
  alt confirm
    Form->>Form: confirmCreateStream()
    Form->>Form: call onCreate(payload)
  else cancel
    Form->>Form: reset preview and state
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #460: Implements the acceptance criteria — fee estimated via Soroban RPC simulateTransaction, preview modal shows amount/rate/network fee, confirmation required before creation, simulation errors shown inline, and tests mock simulation.

Possibly related PRs

Poem

🐰 A rabbit hops to check the fees,
Before the stream flows through the seas,
Soroban whispers the stroops and XLM's cost,
A modal confirms—no surprises are lost!
With hops of clarity, streams now run free.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add create stream fee preview' directly and clearly describes the main feature being added across all changed files.
Linked Issues check ✅ Passed The PR fully addresses all acceptance criteria from issue #460: fee estimation via Soroban RPC simulateTransaction, preview modal with total amount/stream rate/network fee in XLM, Confirm/Cancel actions, error handling, and Vitest coverage with mocked simulation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the fee preview feature: backend endpoint, frontend form flow, API integration, CSS styling for the modal, and comprehensive test coverage—no unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/CreateStreamForm.tsx (1)

223-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle onCreate rejection in confirmCreateStream.

There's no catch here. If onCreate rejects: the modal stays open (setFeePreview(null) is skipped), the promise from void confirmCreateStream() (Line 596) becomes an unhandled rejection, and any apiError the parent renders is inside the <form> behind the modal backdrop — so the failure is invisible to the user, who may just click Confirm again.

Catch the error, surface it, and close the modal so the inline error is visible.

🛡️ Proposed fix
     setIsSubmitting(true);
     setSimulationError(null);
     try {
       await onCreate(feePreview.payload);
       clearDraft();
       setTouched({});
       setSubmitAttempted(false);
       setFeePreview(null);
+    } catch (err) {
+      setSimulationError(
+        err instanceof Error ? err.message : "Failed to create stream.",
+      );
+      setFeePreview(null);
     } finally {
       setIsSubmitting(false);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/CreateStreamForm.tsx` around lines 223 - 237, The
confirmCreateStream function currently lacks a catch for onCreate rejections
which leaves the modal open and skips cleanup (setFeePreview(null),
setIsSubmitting(false) is handled in finally but other state and error display
remain hidden); update confirmCreateStream to await onCreate(feePreview.payload)
inside a try/catch where the catch captures the thrown error, sets a visible
error state (e.g., setSimulationError or a dedicated api error state), ensures
the modal is closed by calling setFeePreview(null) and setSubmitAttempted(false)
in the catch, and then rethrow or return as appropriate; keep the finally block
to setIsSubmitting(false), and reference confirmCreateStream, onCreate,
setFeePreview, setSimulationError, setSubmitAttempted, clearDraft, and
setTouched when applying the changes.
🧹 Nitpick comments (2)
backend/src/index.ts (1)

685-718: ⚡ Quick win

Consider a read/simulation limiter instead of mutationLimiter for fee-estimate.

This endpoint only runs a read-only Soroban simulation, but it shares the mutationLimiter budget (default 10/min). Since the frontend calls estimateCreateStreamFee and then createStream for each creation, every stream now consumes two slots against the mutation limit, effectively halving create throughput to ~5/min. claimableLimiter is already used for the analogous simulation-only getOnChainClaimableAmount path and would be a more consistent fit.

♻️ Use the claimable/simulation limiter
 app.post(
   "/api/streams/fee-estimate",
-  mutationLimiter,
+  claimableLimiter,
   authMiddleware,

The rest of the handler (validation, error normalization, response shape) looks correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 685 - 718, The POST
/api/streams/fee-estimate route is using mutationLimiter but only runs a
read-only Soroban simulation via estimateCreateStreamFee; replace
mutationLimiter with the read/simulation limiter (e.g., claimableLimiter) in the
route middleware list so this simulation consumes the simulation/read budget
instead of mutation budget, keeping authMiddleware and the validation/error
handling unchanged and ensuring any imported limiter symbol (claimableLimiter or
equivalent) is present.
frontend/src/components/CreateStreamForm.tsx (1)

544-605: ⚡ Quick win

Modal lacks keyboard/focus handling.

The dialog declares role="dialog"/aria-modal="true" but focus is not moved into it on open, Escape does not close it, and there's no focus trap (or inert/aria-hidden on the backgrounded form), so keyboard and screen-reader users can tab out behind the modal. At minimum, move focus to the dialog on open and wire Escape to setFeePreview(null).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/CreateStreamForm.tsx` around lines 544 - 605, When
the feePreview modal opens, move keyboard focus into the dialog and close it on
Escape: add a ref (e.g., modalPanelRef) to the element with role="dialog" and in
a useEffect that watches feePreview, save document.activeElement, call
modalPanelRef.current?.focus(), and add a keydown listener that calls
setFeePreview(null) when key === 'Escape'; on cleanup remove the listener and
restore focus to the previously focused element. Also make the dialog panel
programmatically focusable (tabIndex={-1}) and ensure confirmCreateStream and
isSubmitting logic remain unchanged; optionally set aria-hidden/inert on the
background form when feePreview is truthy to prevent tabbing behind the modal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/services/streamStore.ts`:
- Around line 747-750: When rpcServer.simulateTransaction(tx) returns a failure
(the simRes checked by rpc.Api.isSimulationSuccess), include the simulation
failure detail from simRes.error in the thrown error or log instead of the
generic message; locate the simulateTransaction call and the simRes variable and
change the error path to extract simRes.error (from the
SimulateTransactionResponse error variant) and throw or log e.g. `Simulation
failed: ${simRes.error}` so the real RPC failure reason is surfaced.

---

Outside diff comments:
In `@frontend/src/components/CreateStreamForm.tsx`:
- Around line 223-237: The confirmCreateStream function currently lacks a catch
for onCreate rejections which leaves the modal open and skips cleanup
(setFeePreview(null), setIsSubmitting(false) is handled in finally but other
state and error display remain hidden); update confirmCreateStream to await
onCreate(feePreview.payload) inside a try/catch where the catch captures the
thrown error, sets a visible error state (e.g., setSimulationError or a
dedicated api error state), ensures the modal is closed by calling
setFeePreview(null) and setSubmitAttempted(false) in the catch, and then rethrow
or return as appropriate; keep the finally block to setIsSubmitting(false), and
reference confirmCreateStream, onCreate, setFeePreview, setSimulationError,
setSubmitAttempted, clearDraft, and setTouched when applying the changes.

---

Nitpick comments:
In `@backend/src/index.ts`:
- Around line 685-718: The POST /api/streams/fee-estimate route is using
mutationLimiter but only runs a read-only Soroban simulation via
estimateCreateStreamFee; replace mutationLimiter with the read/simulation
limiter (e.g., claimableLimiter) in the route middleware list so this simulation
consumes the simulation/read budget instead of mutation budget, keeping
authMiddleware and the validation/error handling unchanged and ensuring any
imported limiter symbol (claimableLimiter or equivalent) is present.

In `@frontend/src/components/CreateStreamForm.tsx`:
- Around line 544-605: When the feePreview modal opens, move keyboard focus into
the dialog and close it on Escape: add a ref (e.g., modalPanelRef) to the
element with role="dialog" and in a useEffect that watches feePreview, save
document.activeElement, call modalPanelRef.current?.focus(), and add a keydown
listener that calls setFeePreview(null) when key === 'Escape'; on cleanup remove
the listener and restore focus to the previously focused element. Also make the
dialog panel programmatically focusable (tabIndex={-1}) and ensure
confirmCreateStream and isSubmitting logic remain unchanged; optionally set
aria-hidden/inert on the background form when feePreview is truthy to prevent
tabbing behind the modal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1204cf4-8805-4009-8ab7-b13432929b11

📥 Commits

Reviewing files that changed from the base of the PR and between b5e95ce and b033d52.

📒 Files selected for processing (6)
  • backend/src/index.ts
  • backend/src/services/streamStore.ts
  • frontend/src/components/CreateStreamForm.test.tsx
  • frontend/src/components/CreateStreamForm.tsx
  • frontend/src/index.css
  • frontend/src/services/api.ts

Comment on lines +747 to +750
const simRes = await rpcServer.simulateTransaction(tx);
if (!rpc.Api.isSimulationSuccess(simRes)) {
throw new Error("Soroban RPC simulation failed.");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In @stellar/stellar-sdk14.x, does the rpc SimulateTransactionResponse error variant expose anerrorstring field, and isminResourceFee a string on the success variant?

💡 Result:

In the Stellar RPC API (as reflected in @stellar/stellar-sdk), the SimulateTransactionResponse structure handles success and error cases differently. Regarding the error variant, it does expose an error field of type string (often seen as error: string) [1][2]. This field is typically present when the simulation fails, and conversely, fields like minResourceFee are not present in the error response [3][2]. Regarding the success variant, minResourceFee is indeed a string. It is defined as a "Stringified number" representing the recommended minimum resource fee to add when submitting the transaction [3][2].

Citations:


Surface the Soroban RPC simulation failure reason
simulateTransaction’s SimulateTransactionResponse error variant exposes an error: string field; the current generic message hides that detail.

♻️ Include the simulation error detail
   const simRes = await rpcServer.simulateTransaction(tx);
   if (!rpc.Api.isSimulationSuccess(simRes)) {
-    throw new Error("Soroban RPC simulation failed.");
+    throw new Error(
+      "Soroban RPC simulation failed: " +
+        ((simRes as any).error ?? "unknown error"),
+    );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const simRes = await rpcServer.simulateTransaction(tx);
if (!rpc.Api.isSimulationSuccess(simRes)) {
throw new Error("Soroban RPC simulation failed.");
}
const simRes = await rpcServer.simulateTransaction(tx);
if (!rpc.Api.isSimulationSuccess(simRes)) {
throw new Error(
"Soroban RPC simulation failed: " +
((simRes as any).error ?? "unknown error"),
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/streamStore.ts` around lines 747 - 750, When
rpcServer.simulateTransaction(tx) returns a failure (the simRes checked by
rpc.Api.isSimulationSuccess), include the simulation failure detail from
simRes.error in the thrown error or log instead of the generic message; locate
the simulateTransaction call and the simRes variable and change the error path
to extract simRes.error (from the SimulateTransactionResponse error variant) and
throw or log e.g. `Simulation failed: ${simRes.error}` so the real RPC failure
reason is surfaced.

@ritik4ever ritik4ever merged commit bd644c9 into ritik4ever:main Jun 1, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add CreateStreamForm estimated cost preview before submission

2 participants