Add create stream fee preview#554
Conversation
|
@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. |
|
@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! 🚀 |
📝 WalkthroughWalkthroughThis 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. ChangesStream Creation Fee Estimation
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
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 winHandle
onCreaterejection inconfirmCreateStream.There's no
catchhere. IfonCreaterejects: the modal stays open (setFeePreview(null)is skipped), the promise fromvoid confirmCreateStream()(Line 596) becomes an unhandled rejection, and anyapiErrorthe 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 winConsider a read/simulation limiter instead of
mutationLimiterfor fee-estimate.This endpoint only runs a read-only Soroban simulation, but it shares the
mutationLimiterbudget (default 10/min). Since the frontend callsestimateCreateStreamFeeand thencreateStreamfor each creation, every stream now consumes two slots against the mutation limit, effectively halving create throughput to ~5/min.claimableLimiteris already used for the analogous simulation-onlygetOnChainClaimableAmountpath 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 winModal lacks keyboard/focus handling.
The dialog declares
role="dialog"/aria-modal="true"but focus is not moved into it on open,Escapedoes not close it, and there's no focus trap (orinert/aria-hiddenon 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 wireEscapetosetFeePreview(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
📒 Files selected for processing (6)
backend/src/index.tsbackend/src/services/streamStore.tsfrontend/src/components/CreateStreamForm.test.tsxfrontend/src/components/CreateStreamForm.tsxfrontend/src/index.cssfrontend/src/services/api.ts
| const simRes = await rpcServer.simulateTransaction(tx); | ||
| if (!rpc.Api.isSimulationSuccess(simRes)) { | ||
| throw new Error("Soroban RPC simulation failed."); | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.rs/wasi-stellar-rpc-client/latest/wasi_stellar_rpc_client/struct.SimulateTransactionResponse.html
- 2: https://docs.validationcloud.io/v1/stellar/stellar-rpc-formerly-soroban-api/simulatetransaction.md
- 3: https://developers.stellar.org/docs/data/apis/rpc/api-reference/methods/simulateTransaction
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.
| 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.
Summary
Issue
CreateStreamFormestimated cost preview before submission #460Changes
/api/streams/fee-estimateroute that validates the create stream payload and calls Soroban RPCsimulateTransactionthrough the existing stream store context.create_streamoperation shape.CreateStreamFormto estimate on submit, show a confirmation modal, support Cancel/Confirm, and preserve the form when simulation fails.Validation
npm.cmd --prefix frontend test -- --run src/components/CreateStreamForm.test.tsx- passed, 1 file / 8 tests.npm.cmd --prefix frontend run build- failed before this change's code due existing syntax errors infrontend/src/components/IssueBacklog.tsx,IssueBacklog.test.tsx,StreamsTable.tsx, andStreamsTable.test.tsx.npm.cmd --prefix backend run build- failed due existing syntax errors inbackend/src/index.test.ts,backend/src/index.ts, andbackend/src/services/streamStore.ts.npm.cmd --prefix backend test -- --run src/index.test.ts- failed becausebackend/src/index.test.tshas a pre-existing parse error at line 416.Notes
ritik4ever/stellar-bounty-boardwas for the wrong repository because the provided issue number belongs toritik4ever/stellar-stream.Summary by CodeRabbit
Release Notes