Skip to content

fix: reserve fee for unconfidential Liquid claims - #1586

Open
michael1011 wants to merge 1 commit into
mainfrom
liquid-fee-insufficient
Open

fix: reserve fee for unconfidential Liquid claims#1586
michael1011 wants to merge 1 commit into
mainfrom
liquid-fee-insufficient

Conversation

@michael1011

@michael1011 michael1011 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added chain and reverse swap support for unconfidential Liquid destinations, including surcharge-aware amount handling.
    • Enhanced Magic Routing Hint quoting with surcharge-aware receive/send calculations.
  • Bug Fixes
    • “Receive”/status displays now reflect the actual landed amount after claim fees and any unconfidential surcharges.
    • Improved fee estimation and validation for unconfidential Liquid destinations, including more accurate replacement/claim budgeting.
  • Tests
    • Expanded unit, integration, and end-to-end coverage for unconfidential Liquid claim transactions, fee budgeting, and landed-amount verification.

@michael1011
michael1011 requested a review from kiocos July 30, 2026 13:02
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
webapp-mainnet Ready Ready Preview Jul 30, 2026 2:20pm
webapp-pro Ready Ready Preview Jul 30, 2026 2:20pm

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds Liquid unconfidential claim surcharge handling, propagates actual claimed amounts through swap execution, updates fee and UI calculations, and adds unit, regtest, integration, and end-to-end coverage.

Changes

Liquid unconfidential claim handling

Layer / File(s) Summary
Claim surcharge and claimed amount
packages/boltz-swaps/src/utxo/claim.ts, packages/boltz-swaps/src/chain.ts, packages/boltz-swaps/src/reverse.ts
Liquid unconfidential claims reserve the surcharge, return the landed claimedAmount, and propagate it through swap results.
Claim construction validation
packages/boltz-swaps/tests/utxo/*, packages/boltz-swaps/tests/chainUtxo.spec.ts, packages/boltz-swaps/tests/reverse.spec.ts
Tests validate surcharge conditions, output values, relay-fee compliance, value conservation, and claimed-amount propagation.

Quote and display amount integration

Layer / File(s) Summary
Quote and display amount integration
src/utils/*, src/components/*, src/context/Pay.tsx, src/pages/ClaimRescue.tsx, src/status/*
Shared surcharge helpers update fee quotes, Magic Routing Hint amounts, recovery calculations, replacement quotes, rescue amounts, and displayed landed amounts.
Quote and display regression coverage
tests/components/Fees.spec.tsx, tests/context/Pay.spec.tsx, tests/status/*, tests/utils/*
Tests cover surcharge-aware quotes, Magic Routing Hint calculations, recovered amounts, replacement quote persistence, and received-amount rendering.

Integration and end-to-end flows

Layer / File(s) Summary
Integration and end-to-end flows
packages/boltz-swaps/integration/*, e2e/*
Regtest and Playwright tests exercise unconfidential Liquid reverse claims, Magic Routing Hint chain swaps, decoded claim transactions, and received Liquid UTXOs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CreateButton
  participant Fees
  participant ClaimFlow
  participant Liquid
  User->>CreateButton: select unconfidential Liquid destination
  CreateButton->>Fees: quote claim surcharge
  Fees-->>CreateButton: return surcharge-aware fees
  CreateButton->>ClaimFlow: create swap with adjusted amounts
  ClaimFlow->>Liquid: construct claim transaction
  Liquid-->>ClaimFlow: return landed claimed amount
  ClaimFlow-->>User: display landed receive amount
Loading

Possibly related PRs

Suggested reviewers: kiocos

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: reserving fees for unconfidential Liquid claims.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch liquid-fee-insufficient

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 install failed: dependency version conflict. Check your lock file or package.json.


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.

Actionable comments posted: 1

🤖 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 `@src/status/TransactionLockupFailed.tsx`:
- Around line 200-202: Update the surcharge handling in TransactionLockupFailed,
including the corresponding logic at the additional referenced location, so
claimSurchargeForSwap is deducted only when the displayed amount represents the
Boltz claim output. Preserve the full post-position DEX output for
unconfidential Liquid swaps, matching TransactionClaimed’s behavior.
🪄 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: 378a5f2d-d1a0-4a06-bdb1-cddbabd0085f

📥 Commits

Reviewing files that changed from the base of the PR and between 21b0e2a and 137a976.

📒 Files selected for processing (32)
  • e2e/chainSwaps/chainSwaps.spec.ts
  • e2e/chainSwaps/quoteAcceptanceCrash.spec.ts
  • e2e/chainSwaps/zeroAmount.spec.ts
  • e2e/reverseSwap.spec.ts
  • e2e/utils.ts
  • packages/boltz-swaps/integration/regtest.ts
  • packages/boltz-swaps/integration/reverseSwap.regtest.spec.ts
  • packages/boltz-swaps/src/chain.ts
  • packages/boltz-swaps/src/reverse.ts
  • packages/boltz-swaps/src/utxo/claim.ts
  • packages/boltz-swaps/src/utxo/index.ts
  • packages/boltz-swaps/tests/chainUtxo.spec.ts
  • packages/boltz-swaps/tests/reverse.spec.ts
  • packages/boltz-swaps/tests/utxo/claim.spec.ts
  • packages/boltz-swaps/tests/utxo/claimReverse.spec.ts
  • packages/boltz-swaps/tests/utxo/claimUnconfidential.spec.ts
  • packages/boltz-swaps/tests/utxo/claimUnconfidentialEndToEnd.spec.ts
  • src/components/CreateButton.tsx
  • src/components/Fees.tsx
  • src/components/OptimizedRoute.tsx
  • src/context/Pay.tsx
  • src/pages/ClaimRescue.tsx
  • src/status/TransactionClaimed.tsx
  • src/status/TransactionLockupFailed.tsx
  • src/utils/magicRoutingHint.ts
  • src/utils/unconfidential.ts
  • tests/components/Fees.spec.tsx
  • tests/context/Pay.spec.tsx
  • tests/status/TransactionClaimed.spec.tsx
  • tests/status/TransactionLockupFailedAutoAccept.spec.tsx
  • tests/utils/magicRoutingHint.spec.ts
  • tests/utils/unconfidential.spec.ts

Comment thread src/status/TransactionLockupFailed.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.

Caution

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

⚠️ Outside diff range comments (1)
src/status/TransactionClaimed.tsx (1)

95-111: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard post-bridge swaps when subtracting the claim surcharge.

TransactionLockupFailed.tsx skips the surcharge whenever getFinalAssetReceive(...) !== assetReceive, because post-position DEX or bridge outputs are in a different asset. TransactionClaimed.tsx only checks the post-position DEX case, so a Reverse/Chain swap with bridge.position === SwapPosition.Post still subtracts claimSurchargeForSwap() from the persisted receiveAmount, while receiveDenomination() formats the bridge destination asset. Mirror the final-asset guard instead of only excluding post-DEX.

🤖 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 `@src/status/TransactionClaimed.tsx` around lines 95 - 111, The receiveAmount
function should skip claimSurchargeForSwap whenever the final receive asset
differs from the original asset, including post-position bridge and DEX outputs.
Mirror the final-asset guard used by TransactionLockupFailed.tsx while
preserving the existing post-position DEX quoteAmount handling and
receiveDenomination-compatible formatting.
🧹 Nitpick comments (3)
packages/boltz-swaps/src/utxo/claim.ts (1)

328-366: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider noting the divergence between unconfidentialClaimSurcharge and needsBlindedOpReturn.

The exported helper decides purely from the destination address, while the claim path additionally requires blinded inputs. For an unblinded Liquid lockup, the quote reserves 6 sat that the claim never spends (user simply lands 6 sat more), so this is benign — but a short comment on the exported helper would prevent it from being read as an exact mirror of the claim-side condition.

🤖 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 `@packages/boltz-swaps/src/utxo/claim.ts` around lines 328 - 366, The exported
unconfidentialClaimSurcharge helper should explicitly document that it is
destination-based and may differ from the claim-side needsBlindedOpReturn
condition, which also requires blinded inputs. Add a brief comment clarifying
that unblinded Liquid lockups may still receive the reserved 6 sat without
spending it, while preserving the existing behavior.
packages/boltz-swaps/integration/reverseSwap.regtest.spec.ts (1)

189-219: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unexplained -1 in fee assertion.

claimFee + liquidUnconfidentialClaimExtra - 1 lacks a comment explaining the off-by-one. The sibling test at Line 221-222 documents its rationale; a similar one-liner here (likely relay-fee truncation, mirrored in e2e/reverseSwap.spec.ts's "truncating division" comment) would help future maintainers understand the exact expected value isn't accidental.

🤖 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 `@packages/boltz-swaps/integration/reverseSwap.regtest.spec.ts` around lines
189 - 219, The fee assertion in the “is accepted by Elements and pays the
surcharge out of the claim output” test needs an inline comment explaining the
intentional -1 adjustment. Add a concise rationale referencing relay-fee
truncation, matching the documented explanation in the sibling test and mirrored
reverse-swap coverage, without changing the expected fee value.
e2e/utils.ts (1)

497-508: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicates waitForUTXOs's axios call; consider consolidating.

getAddressUtxos re-implements the same GET .../address/${address}/utxo call already used by waitForUTXOs (Lines 509-528), and re-declares an inline UTXO shape instead of reusing the existing UTXO type. Consider having waitForUTXOs call getAddressUtxos internally to avoid drift between the two.

♻️ Proposed consolidation
 export const getAddressUtxos = async (
     asset: AssetType,
     address: string,
-): Promise<{ txid: string; vout: number; value?: number }[]> =>
+): Promise<UTXO[]> =>
     (
-        await axios.get<{ txid: string; vout: number; value?: number }[]>(
+        await axios.get<UTXO[]>(
             `${config.assets![asset].blockExplorerApis![0].normal}/address/${address}/utxo`,
         )
     ).data;

 export const waitForUTXOs = async (
     asset: AssetType,
     address: string,
     amount: number,
 ) => {
     await expect
-        .poll(
-            async () => {
-                const utxos = (
-                    await axios.get<UTXO[]>(
-                        `${config.assets![asset].blockExplorerApis![0].normal}/address/${address}/utxo`,
-                    )
-                ).data;
-
-                return utxos.length === amount;
-            },
-            { timeout: 30_000 },
-        )
+        .poll(async () => (await getAddressUtxos(asset, address)).length === amount, {
+            timeout: 30_000,
+        })
         .toBe(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 `@e2e/utils.ts` around lines 497 - 508, Consolidate the duplicated explorer
request by updating waitForUTXOs to call getAddressUtxos instead of issuing its
own axios GET to the address UTXO endpoint. Reuse the existing UTXO type for
getAddressUtxos and waitForUTXOs so both paths share the same response shape and
cannot drift.
🤖 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.

Outside diff comments:
In `@src/status/TransactionClaimed.tsx`:
- Around line 95-111: The receiveAmount function should skip
claimSurchargeForSwap whenever the final receive asset differs from the original
asset, including post-position bridge and DEX outputs. Mirror the final-asset
guard used by TransactionLockupFailed.tsx while preserving the existing
post-position DEX quoteAmount handling and receiveDenomination-compatible
formatting.

---

Nitpick comments:
In `@e2e/utils.ts`:
- Around line 497-508: Consolidate the duplicated explorer request by updating
waitForUTXOs to call getAddressUtxos instead of issuing its own axios GET to the
address UTXO endpoint. Reuse the existing UTXO type for getAddressUtxos and
waitForUTXOs so both paths share the same response shape and cannot drift.

In `@packages/boltz-swaps/integration/reverseSwap.regtest.spec.ts`:
- Around line 189-219: The fee assertion in the “is accepted by Elements and
pays the surcharge out of the claim output” test needs an inline comment
explaining the intentional -1 adjustment. Add a concise rationale referencing
relay-fee truncation, matching the documented explanation in the sibling test
and mirrored reverse-swap coverage, without changing the expected fee value.

In `@packages/boltz-swaps/src/utxo/claim.ts`:
- Around line 328-366: The exported unconfidentialClaimSurcharge helper should
explicitly document that it is destination-based and may differ from the
claim-side needsBlindedOpReturn condition, which also requires blinded inputs.
Add a brief comment clarifying that unblinded Liquid lockups may still receive
the reserved 6 sat without spending it, while preserving the existing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a257c8be-623e-4283-9761-a24b2d1494c9

📥 Commits

Reviewing files that changed from the base of the PR and between 137a976 and e55829d.

📒 Files selected for processing (32)
  • e2e/chainSwaps/chainSwaps.spec.ts
  • e2e/chainSwaps/quoteAcceptanceCrash.spec.ts
  • e2e/chainSwaps/zeroAmount.spec.ts
  • e2e/reverseSwap.spec.ts
  • e2e/utils.ts
  • packages/boltz-swaps/integration/regtest.ts
  • packages/boltz-swaps/integration/reverseSwap.regtest.spec.ts
  • packages/boltz-swaps/src/chain.ts
  • packages/boltz-swaps/src/reverse.ts
  • packages/boltz-swaps/src/utxo/claim.ts
  • packages/boltz-swaps/src/utxo/index.ts
  • packages/boltz-swaps/tests/chainUtxo.spec.ts
  • packages/boltz-swaps/tests/reverse.spec.ts
  • packages/boltz-swaps/tests/utxo/claim.spec.ts
  • packages/boltz-swaps/tests/utxo/claimReverse.spec.ts
  • packages/boltz-swaps/tests/utxo/claimUnconfidential.spec.ts
  • packages/boltz-swaps/tests/utxo/claimUnconfidentialEndToEnd.spec.ts
  • src/components/CreateButton.tsx
  • src/components/Fees.tsx
  • src/components/OptimizedRoute.tsx
  • src/context/Pay.tsx
  • src/pages/ClaimRescue.tsx
  • src/status/TransactionClaimed.tsx
  • src/status/TransactionLockupFailed.tsx
  • src/utils/magicRoutingHint.ts
  • src/utils/unconfidential.ts
  • tests/components/Fees.spec.tsx
  • tests/context/Pay.spec.tsx
  • tests/status/TransactionClaimed.spec.tsx
  • tests/status/TransactionLockupFailedAutoAccept.spec.tsx
  • tests/utils/magicRoutingHint.spec.ts
  • tests/utils/unconfidential.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/context/Pay.spec.tsx
  • packages/boltz-swaps/src/utxo/index.ts
  • packages/boltz-swaps/tests/utxo/claimReverse.spec.ts
  • packages/boltz-swaps/tests/chainUtxo.spec.ts

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.

1 participant