Skip to content

feat: add FeeRateField component to set fee rates for swaps - #2432

Open
im-adithya wants to merge 10 commits into
masterfrom
fix/onchain-fee-rate-field
Open

feat: add FeeRateField component to set fee rates for swaps#2432
im-adithya wants to merge 10 commits into
masterfrom
fix/onchain-fee-rate-field

Conversation

@im-adithya

@im-adithya im-adithya commented Jun 10, 2026

Copy link
Copy Markdown
Member

Fixes #2429

Summary by CodeRabbit

  • New Features
    • Added an editable on-chain fee field with view/edit mode, automatic initialization from recommended fees, validation, priority quick-selects, fee-estimate error hinting, and an optional “View on Mempool”.
    • Internal swap-in requests can now include a fee rate, and the internal flow surfaces it in the shared fee field.
  • Refactor
    • Unified fee-rate editing across withdraw, on-chain send, and swap flows using the shared component.
  • Bug Fixes
    • Updated on-chain wording and hyphenation (e.g., “Confirmed on-chain”).

@im-adithya
im-adithya requested a review from rolznz June 10, 2026 09:30
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a shared fee-rate field, threads internal swap fee-rate data through the frontend and backend, and updates several on-chain labels and messages.

Changes

Fee Rate UI and Internal Swap Flow

Layer / File(s) Summary
FeeRateField component
frontend/src/components/FeeRateField.tsx
Defines the shared fee-rate field with controlled value updates, edit/view state, recommended-fee initialization, error-driven edit mode, quick-select presets, and optional mempool link.
Withdraw fee-rate UI
frontend/src/screens/wallet/WithdrawOnchainFunds.tsx
Replaces the withdraw screen’s inline advanced fee controls with FeeRateField, removes mempool-specific state and loading gates, and updates withdraw copy and confirmation text.
Send fee-rate UI
frontend/src/screens/wallet/send/Onchain.tsx
Replaces the on-chain send form’s inline fee-rate editor with FeeRateField, removes local mempool-editing state, and updates the fee heading label.
Swap form and status fee-rate flow
frontend/src/screens/wallet/swap/index.tsx, frontend/src/screens/wallet/swap/SwapInStatus.tsx
Adds internal-swap fee-rate state in the swap form, renders FeeRateField for internal swaps, includes feeRate in the swap request payload, keeps internal status navigation on ?internal=true, and updates swap-in status wording and URL-state handling.
Swap API and service threading
api/models.go, api/api.go, frontend/src/types.ts, swaps/swaps_service.go
Extends the swap-in request model with internalPayment and optional feeRate, forwards those fields through the API handler, expands the swap service interface and implementation signature, and conditionally redeems on-chain funds for internal swaps while persisting the lockup transaction id.
On-chain wording updates
frontend/src/components/CloseChannelDialogContent.tsx, frontend/src/screens/channels/CurrentChannelOrder.tsx, frontend/src/screens/wallet/swap/SwapOutStatus.tsx
Updates on-chain wording in the channel dialog and deposit order text, and corrects the swap-out confirmation label to use hyphenated “on-chain”.

Sequence Diagram(s)

sequenceDiagram
  participant SwapInForm
  participant InitiateSwapIn API
  participant SwapsService
  participant LnClient
  participant SwapInStatus

  SwapInForm->>InitiateSwapIn API: submit internalPayment + feeRate
  InitiateSwapIn API->>SwapsService: SwapIn(amountSat, autoSwap, internalPayment, feeRate)
  SwapsService->>LnClient: RedeemOnchainFunds(..., feeRate)
  LnClient-->>SwapsService: lockupTxId
  SwapsService-->>InitiateSwapIn API: SwapResponse
  SwapInForm->>SwapInStatus: navigate with ?internal=true
  SwapInStatus->>SwapInStatus: derive isInternalSwap from search params
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • getAlby/hub#2271: Touches the same swaps/swaps_service.go swap-in path and signature plumbing.
  • getAlby/hub#2421: Related swap-in internal redemption flow and frontend/backend threading changes.

Suggested Reviewers

  • rolznz

Poem

🐰 A fee field hopped in, neat and bright,
Internal swaps now steer by user light.
On-chain words got their tidy little dash,
The rabbit nods: “Yes, that’s a nice code splash!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 is concise and accurately describes the main addition of a shared fee-rate field for swaps.
Linked Issues check ✅ Passed The PR covers fee-rate control for internal swaps, manual fallback, shared fee UI, and selection before confirmation.
Out of Scope Changes check ✅ Passed The changes stay focused on fee-rate handling and related on-chain wording/UI updates, with no clear unrelated additions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/onchain-fee-rate-field

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.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
frontend/src/screens/wallet/swap/index.tsx (1)

320-331: 💤 Low value

Consider adding aria-label for better accessibility.

Similar to the send flow, this button triggers fee rate editing but lacks an explicit accessible label. Adding aria-label="Edit fee rate" would improve screen reader experience.

♿ Suggested accessibility improvement
 <button
   type="button"
   className="flex items-center gap-2 cursor-pointer"
+  aria-label="Edit fee rate"
   onClick={() => setEditFee(true)}
 >
🤖 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/screens/wallet/swap/index.tsx` around lines 320 - 331, The
fee-edit button in the Swap screen (the button using setEditFee, feeRate and
rendering PencilIcon) lacks an accessible name; add an aria-label (e.g.,
aria-label="Edit fee rate") to the button element so screen readers announce its
purpose, and ensure the label remains accurate when feeRate is loading (the
Loading state) or present.
frontend/src/screens/wallet/send/Onchain.tsx (1)

214-225: 💤 Low value

Consider adding aria-label for better accessibility.

The button element triggers edit mode but lacks an explicit accessible label. While the button contains visual content (fee rate text and pencil icon), an aria-label="Edit fee rate" would improve screen reader experience by clearly announcing the button's purpose.

♿ Suggested accessibility improvement
 <button
   type="button"
   className="flex items-center gap-2 cursor-pointer"
+  aria-label="Edit fee rate"
   onClick={() => setEditFee(true)}
 >
🤖 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/screens/wallet/send/Onchain.tsx` around lines 214 - 225, The
button that toggles edit mode (onClick={() => setEditFee(true)}) lacks an
accessible label; update the <button> element that renders feeRate/Loading and
the PencilIcon to include aria-label="Edit fee rate" (or similar) so screen
readers announce its purpose, ensuring the interactive element remains unchanged
otherwise and still uses setEditFee, feeRate, Loading and PencilIcon as before.
🤖 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.

Nitpick comments:
In `@frontend/src/screens/wallet/send/Onchain.tsx`:
- Around line 214-225: The button that toggles edit mode (onClick={() =>
setEditFee(true)}) lacks an accessible label; update the <button> element that
renders feeRate/Loading and the PencilIcon to include aria-label="Edit fee rate"
(or similar) so screen readers announce its purpose, ensuring the interactive
element remains unchanged otherwise and still uses setEditFee, feeRate, Loading
and PencilIcon as before.

In `@frontend/src/screens/wallet/swap/index.tsx`:
- Around line 320-331: The fee-edit button in the Swap screen (the button using
setEditFee, feeRate and rendering PencilIcon) lacks an accessible name; add an
aria-label (e.g., aria-label="Edit fee rate") to the button element so screen
readers announce its purpose, and ensure the label remains accurate when feeRate
is loading (the Loading state) or present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d92108aa-1cd1-43e6-82f8-4dc3bab82dbd

📥 Commits

Reviewing files that changed from the base of the PR and between cc429c4 and 76767c9.

📒 Files selected for processing (5)
  • frontend/src/components/FeeRateField.tsx
  • frontend/src/screens/wallet/WithdrawOnchainFunds.tsx
  • frontend/src/screens/wallet/send/Onchain.tsx
  • frontend/src/screens/wallet/swap/SwapInStatus.tsx
  • frontend/src/screens/wallet/swap/index.tsx

Comment thread frontend/src/screens/wallet/send/Onchain.tsx Outdated
Comment thread frontend/src/screens/wallet/swap/SwapInStatus.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/src/components/FeeRateField.tsx (1)

30-39: 💤 Low value

Consider streamlining the dependency array.

The effect includes onFeeRateChange in the dependency array. Since onFeeRateChange is setFeeRate from useState (which is stable across renders), including it may cause unnecessary effect re-evaluation. While React 19 tolerates this, you could exclude it for cleaner deps:

-  }, [feeRate, onFeeRateChange, recommendedFees]);
+  }, [feeRate, recommendedFees]);

Alternatively, if you want to keep the dep for explicitness, add an ESLint disable comment to document the intentional inclusion.

🤖 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/FeeRateField.tsx` around lines 30 - 39, The useEffect
that sets the default fee currently lists onFeeRateChange in its dependency
array even though it is the stable state setter (setFeeRate), causing an
unnecessary dep; update the dependency array for the effect that references
useEffect, hasInitializedDefaultFee, recommendedFees, feeRate, and
onFeeRateChange by removing onFeeRateChange (leaving [feeRate, recommendedFees])
to avoid spurious re-runs, or if you prefer to keep it for explicitness, add an
inline ESLint disable comment (e.g., // eslint-disable-next-line
react-hooks/exhaustive-deps) immediately above the useEffect to document the
intentional inclusion.
frontend/src/screens/wallet/send/Onchain.tsx (1)

111-159: 💤 Low value

Consider adding defensive validation for feeRate.

OnchainForm submits when feeRate is converted to a number (line 129: feeRate: +feeRate). If feeRate is an empty string (e.g., if the user submits before FeeRateField auto-initializes), +"" evaluates to 0, which may be rejected by the API or cause unexpected behavior.

While the UX mitigates this (FeeRateField shows a loading state and auto-initializes quickly, plus HTML5 required validation in edit mode), adding explicit validation would be more defensive:

🛡️ Suggested defensive check
 const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
   event.preventDefault();
   try {
     if (!balances) {
       return;
     }
+    if (!feeRate || +feeRate <= 0) {
+      throw new Error("Please wait for fee rate to load or enter a valid fee rate.");
+    }
     if (balances.onchain.spendableSat <= ONCHAIN_DUST_SATS) {
       throw new Error(
         "You currently don't have enough sats to pay for an on-chain transaction. Consider swapping from Lightning Balance."
       );
     }
🤖 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/screens/wallet/send/Onchain.tsx` around lines 111 - 159, The
onSubmit handler currently converts feeRate with +feeRate which turns an empty
string into 0; update onSubmit to defensively validate feeRate before creating
the RedeemOnchainFundsRequest: ensure feeRate is present, parsable to a positive
number (e.g., Number(feeRate) > 0), show a user-facing error (toast.error) and
return early (ensuring setLoading(false) is called) if validation fails;
reference the feeRate React state, the onSubmit function, and the
RedeemOnchainFundsRequest payload when making this change.
🤖 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 `@frontend/src/screens/wallet/send/Onchain.tsx`:
- Around line 189-191: OnchainForm currently renders FeeRateField
unconditionally; mirror the conditional used in WithdrawOnchainFunds so fee rate
input is only shown for backends that support it (check backendType === "LDK" ||
backendType === "LND"). Update the JSX in OnchainForm to wrap FeeRateField (and
use of setFeeRate/feeRate state) in the same backendType conditional, ensuring
RedeemOnchainFundsRequest.feeRate remains optional for unsupported backends and
no feeRate is sent when backendType is not LDK or LND.

---

Nitpick comments:
In `@frontend/src/components/FeeRateField.tsx`:
- Around line 30-39: The useEffect that sets the default fee currently lists
onFeeRateChange in its dependency array even though it is the stable state
setter (setFeeRate), causing an unnecessary dep; update the dependency array for
the effect that references useEffect, hasInitializedDefaultFee, recommendedFees,
feeRate, and onFeeRateChange by removing onFeeRateChange (leaving [feeRate,
recommendedFees]) to avoid spurious re-runs, or if you prefer to keep it for
explicitness, add an inline ESLint disable comment (e.g., //
eslint-disable-next-line react-hooks/exhaustive-deps) immediately above the
useEffect to document the intentional inclusion.

In `@frontend/src/screens/wallet/send/Onchain.tsx`:
- Around line 111-159: The onSubmit handler currently converts feeRate with
+feeRate which turns an empty string into 0; update onSubmit to defensively
validate feeRate before creating the RedeemOnchainFundsRequest: ensure feeRate
is present, parsable to a positive number (e.g., Number(feeRate) > 0), show a
user-facing error (toast.error) and return early (ensuring setLoading(false) is
called) if validation fails; reference the feeRate React state, the onSubmit
function, and the RedeemOnchainFundsRequest payload when making this change.
🪄 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: 8b6c2f5f-88fd-41ee-8064-bdc906dcdc02

📥 Commits

Reviewing files that changed from the base of the PR and between 76767c9 and cc4577a.

📒 Files selected for processing (8)
  • frontend/src/components/CloseChannelDialogContent.tsx
  • frontend/src/components/FeeRateField.tsx
  • frontend/src/screens/channels/CurrentChannelOrder.tsx
  • frontend/src/screens/wallet/WithdrawOnchainFunds.tsx
  • frontend/src/screens/wallet/send/Onchain.tsx
  • frontend/src/screens/wallet/swap/SwapInStatus.tsx
  • frontend/src/screens/wallet/swap/SwapOutStatus.tsx
  • frontend/src/screens/wallet/swap/index.tsx
✅ Files skipped from review due to trivial changes (3)
  • frontend/src/components/CloseChannelDialogContent.tsx
  • frontend/src/screens/wallet/swap/SwapOutStatus.tsx
  • frontend/src/screens/channels/CurrentChannelOrder.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/screens/wallet/swap/SwapInStatus.tsx
  • frontend/src/screens/wallet/swap/index.tsx

Comment thread frontend/src/screens/wallet/send/Onchain.tsx
`/wallet/swap/in/status/${swapInResponse.swapId}${swapFrom === "internal" ? "?internal=true" : ""}`
`/wallet/swap/in/status/${swapInResponse.swapId}${
swapFrom === "internal"
? `?internal=true&feeRate=${encodeURIComponent(feeRate)}`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't it weird actually we pass the fee rate here rather than to the swap endpoint itself when creating the swap?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Then we can remove this effect that runs on page load (which is potentially a bit dodgy)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For this we should add a new parameter (e.g. ?internal=true to the api call) so that by default it works the same as now (for backward compatibility / hub CLI)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added it to InitiateSwapRequest as InternalPayment

@rolznz

rolznz commented Jun 11, 2026

Copy link
Copy Markdown
Member

Nice change! 💪

@rolznz rolznz mentioned this pull request Jun 23, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 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 `@api/api.go`:
- Around line 1053-1058: The SwapIn flow in api.go forwards user-provided
FeeRate directly into wallet spending without validation. Add boundary
validation in the swap initiation path before calling GetSwapsService().SwapIn,
rejecting FeeRate values of 0 and capping excessive values to an acceptable
maximum, while keeping the service call itself trusted. Use the existing
initiateSwapInRequest handling and SwapIn call site to place the check, and
return a clear error before any funding attempt if the FeeRate is invalid.

In `@frontend/src/screens/wallet/swap/index.tsx`:
- Around line 135-140: Avoid sending a coerced fee rate in the swap payload: the
feeRate handling in the internalPayment branch currently uses parseInt(feeRate),
which can truncate values like decimals and turn invalid input into NaN/null.
Update the payload-building logic in the swap screen to validate feeRate as a
positive integer before including it, and only add the feeRate field when the
value is explicitly valid.

In `@swaps/swaps_service.go`:
- Around line 501-524: The internal payment path in `swapIn` should not return a
failure after `svc.lnClient.RedeemOnchainFunds` has already broadcast the lockup
transaction. Keep the successfully returned `lockupTxId` and, if the
`svc.db.Model(&dbSwap).Updates(...)` call fails, log the DB error but continue
so the swap is not marked failed and `startSwapInListener` can still run. Update
the `internalPayment` block to preserve the funded swap state using
`dbSwap.LockupTxId`/`lockupTxId` even when persistence fails.
🪄 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: af616334-3e4c-4c59-986b-497d38b853c9

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1e794 and d97a5bf.

📒 Files selected for processing (6)
  • api/api.go
  • api/models.go
  • frontend/src/screens/wallet/swap/SwapInStatus.tsx
  • frontend/src/screens/wallet/swap/index.tsx
  • frontend/src/types.ts
  • swaps/swaps_service.go

Comment thread api/api.go
Comment thread frontend/src/screens/wallet/swap/index.tsx
Comment thread swaps/swaps_service.go
Comment thread api/models.go
Comment thread frontend/src/screens/wallet/swap/index.tsx Outdated

@rolznz rolznz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

utACK, one minor comment

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.

Allow user to configure fee rate when doing internal swaps

2 participants