fix(web): deduct 2.0 XLM reserve when auto-filling MAX amount (#411) - #543
fix(web): deduct 2.0 XLM reserve when auto-filling MAX amount (#411)#543Mhidesav wants to merge 1 commit into
Conversation
…le-Protocol#411) When clicking MAX for XLM in the payment stream form, the full XLM balance was populated, leaving 0 XLM for transaction fees and the Stellar minimum account reserve. Now deducts 2.0 XLM so users can still pay fees.
📝 WalkthroughWalkthroughChangesPayment stream creation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant PaymentStreamForm
participant CreatePaymentStream
participant StellarService
participant QueryClient
PaymentStreamForm->>CreatePaymentStream: submit validated stream data
CreatePaymentStream->>StellarService: createPaymentStream()
StellarService-->>CreatePaymentStream: success or error
CreatePaymentStream->>QueryClient: invalidate stream queries
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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)
apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx (1)
207-212: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPrevent duplicate stream creation requests.
isSubmittingupdates asynchronously, so two rapid confirmation events can both pass through and submit separate non-idempotent stream creations. Add a synchronous ref lock before closing the modal and release it infinally.Proposed fix
-import { useEffect, useMemo, useState, useCallback } from "react"; +import { useEffect, useMemo, useState, useCallback, useRef } from "react"; +const submissionLockRef = useRef(false); const handleConfirmStream = async () => { + if (submissionLockRef.current) return; + submissionLockRef.current = true; + setIsSubmitting(true); setShowConfirmationModal(false); try { - setIsSubmitting(true); // ... } finally { + submissionLockRef.current = false; 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 `@apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx` around lines 207 - 212, Update handleConfirmStream to use a synchronous ref-based submission lock checked and set before closing the confirmation modal, returning immediately when already locked; release the lock in the existing finally block so failed or completed requests allow another submission.
🤖 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 `@apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx`:
- Around line 152-156: Return null instead of the string "0" from the XLM
maximum calculation in CreatePaymentStream when afterReserve is non-positive. In
PaymentStreamForm, render the MAX control only when the parsed maximum is a
positive value, preserving the existing behavior for valid positive limits.
---
Outside diff comments:
In `@apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx`:
- Around line 207-212: Update handleConfirmStream to use a synchronous ref-based
submission lock checked and set before closing the confirmation modal, returning
immediately when already locked; release the lock in the existing finally block
so failed or completed requests allow another submission.
🪄 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 Plus
Run ID: 55375c79-dda6-4ea8-810b-49f7d207ff66
📒 Files selected for processing (2)
apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsxapps/web/src/components/modules/payment-stream/PaymentStreamForm.tsx
| if (streamData.token === "XLM") { | ||
| const afterReserve = balanceNum - XLM_RESERVE; | ||
| if (afterReserve <= 0) return "0"; | ||
| // Format to 7 decimal places (Stroop precision), trim trailing zeros | ||
| return afterReserve.toFixed(7).replace(/0+$/, "").replace(/\.$/, ""); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Hide MAX when no XLM remains after the reserve. For 0 < balance <= 2.0, the computed value is "0", which is truthy; the form renders MAX and clicking it fills an invalid zero amount.
apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx#L152-L156: returnnullrather than"0"whenafterReserve <= 0.apps/web/src/components/modules/payment-stream/PaymentStreamForm.tsx#L115-L123: render MAX only for a positive parsed maximum as a defensive UI check.
📍 Affects 2 files
apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx#L152-L156(this comment)apps/web/src/components/modules/payment-stream/PaymentStreamForm.tsx#L115-L123
🤖 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 `@apps/web/src/components/modules/payment-stream/CreatePaymentStream.tsx`
around lines 152 - 156, Return null instead of the string "0" from the XLM
maximum calculation in CreatePaymentStream when afterReserve is non-positive. In
PaymentStreamForm, render the MAX control only when the parsed maximum is a
positive value, preserving the existing behavior for valid positive limits.
Summary
Resolves #411 — Clicking MAX for XLM in the payment stream form left 0 XLM in the wallet, making it impossible to pay transaction fees or maintain the Stellar minimum account reserve.
Problem
When a user clicked "MAX" to auto-fill the total amount for an XLM payment stream, the entire XLM balance was populated into the amount field. This left the wallet with 0 XLM — not enough to cover:
In practice, this meant users clicking MAX would see an "Insufficient XLM balance" error when trying to submit, with no clear explanation of why.
Solution
Deduct 2.0 XLM from the available balance when populating the MAX amount for XLM streams. This 2.0 XLM cushion covers:
For non-XLM tokens (USDC, AQUA, etc.), the full token balance is used as before — since the XLM reserve concerns are handled separately by the wallet's native XLM balance.
Changes
apps/web/src/components/modules/payment-stream/PaymentStreamForm.tsxmaxBalance?: string | nullandonMaxClick?: () => voidoptional props toStreamFormPropsrelativecontainerapps/web/src/components/modules/payment-stream/CreatePaymentStream.tsxXLM_RESERVE = 2.0constant with documentationmaxAmountmemoized computation:Math.max(0, balance - 2.0)formatted to 7 decimal places (Stroop precision) with trailing zeros trimmedhandleMaxClickcallback that sets the stream amount viasetStreamDatamaxBalanceandonMaxClickprops down toPaymentStreamFormTesting & Validation
tsc --noEmit)distribution/page.tsxare unrelated)react-hooks/exhaustive-depswarning)stellar.service.*.test.ts, unrelated)Screenshots / Behavior
Before: Clicking MAX with 10.5 XLM balance → amount set to
10.5, wallet left with 0 XLM → transaction fails.After: Clicking MAX with 10.5 XLM balance → amount set to
8.5, wallet keeps 2.0 XLM → transaction succeeds.Related
use-distribution-transaction.ts) already reserves 1.0 XLM — this fix uses a more conservative 2.0 XLM for payment streams which have higher Soroban gas costs.OfframpForm.tsx.Summary by CodeRabbit
New Features
Bug Fixes