Skip to content

mobile: support exact miner fee for refund transactions - #7

Merged
kaloudis merged 3 commits into
ZeusLN:lnd-v0.21.4-beta-zeus.1from
shubhamkmr04:swaps-updates
Oct 15, 2026
Merged

kaloudis merged 3 commits into
ZeusLN:lnd-v0.21.4-beta-zeus.1from
shubhamkmr04:swaps-updates

Conversation

@shubhamkmr04

@shubhamkmr04 shubhamkmr04 commented May 7, 2026 •

Copy link
Copy Markdown
  • Adds minerFee to BuildRefundTransaction, so a refund can pay an exact fee in sats. 0 keeps the fee rate. CreateRefundTransaction keeps its signature and passes 0.
  • Claims and refunds now return an error instead of a transaction with no output or one below dust.

@shubhamkmr04 shubhamkmr04 changed the title mobile: support exact miner fee for refund transactions mobile: support exact miner fee for refund transactions and refactor swap helpers May 7, 2026
@kaloudis

kaloudis commented Oct 8, 2026

Copy link
Copy Markdown

Holding this for v13.3.0 rather than the lnd-v0.21.4-beta-zeus.1 release (v13.2.3).

Once #10 is in, the simplest route for the exact refund fee is a minerFee parameter on BuildRefundTransaction. CreateRefundTransaction can then stay as it is for older app builds. Exact fees for reverse claims are already in the .1 series.

@kaloudis

Copy link
Copy Markdown

@shubhamkmr04 could you rebase this?

@shubhamkmr04 shubhamkmr04 changed the title mobile: support exact miner fee for refund transactions and refactor swap helpers mobile: support exact miner fee for refund transactions Oct 13, 2026
@shubhamkmr04
shubhamkmr04 changed the base branch from lnd-v0.20.1-beta-zeus.1 to lnd-v0.21.4-beta-zeus.1 October 13, 2026 14:49
@shubhamkmr04

shubhamkmr04 commented Oct 13, 2026 •

Copy link
Copy Markdown
Author

Rebased onto lnd-v0.21.4-beta-zeus.1. #10 already covered the helper refactor, so this is now one commit: minerFee on both refund functions, with a shared swapFee helper and a test.

@kaloudis kaloudis left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One bug: an exact minerFee at or above the lockup value produces an unbroadcastable transaction with a nil error. Details inline.

Comment thread mobile/swaps.go
},
},
boltz.Fee{SatsPerVbyte: &satPerVbyte},
swapFee(feeRate, minerFee),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With a sats fee, nothing checks minerFee against the lockup value, and the Results from boltz.ConstructTransaction are discarded on line 234.

In boltz-client (pkg/boltz/transaction.go:134-140, same in v2.9.0 and v2.12.5), when the value is less than the fee, getOutValues only records value less than fee in Results and skips the output. constructBtcTransaction then builds a transaction with no outputs, and for a non-cooperative refund it signs the input and returns err == nil. When the fee equals the value, the check passes and a 0-sat output is added.

Reproduced on this branch with the existing newRefundFixture (100000 sat lockup), non-cooperative:

minerFee err outputs
100000 nil 1 output, value 0
150000 nil none

Neither transaction relays (bad-txns-vout-empty / dust), and CreateRefundTransaction passes the mempool.space rejection back with no hint of the cause. Before this PR the refund only took a fee rate, so this needed an absurd rate.

Suggested fix:

  1. Keep the results (refundTransaction, results, err := ...) and return results[id].Err when it is set.
  2. Reject a minerFee that leaves the output below dust (330 sat for P2TR, 294 for P2WPKH), since Results has no error for the equal-value case.

A test calling BuildRefundTransaction with minerFee of 100000 and 150000 against the fixture, expecting an error, would cover both cases.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 08c2b5f. Both builders now return the error ConstructTransaction records in results, and reject a transaction with no output or one below dust (mempool.IsDust at the default relay fee, so 330/294). Tests cover 99800, 100000 and 150000 against the fixture.

Comment thread mobile/swaps.go
},
},
fee,
swapFee(feeRate, minerFee),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same issue here: results from ConstructTransaction (line 169) is discarded. If Boltz refuses the cooperative claim, boltz-client falls back to a non-cooperative claim, which can return the same empty-output or 0-value transaction with a nil error when minerFee is at or above the lockup value.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 08c2b5f with the same check. A test claims against a host that refuses to co-sign, with minerFee of 100000 and 150000, and expects an error.

Comment thread mobile/swaps.go Outdated
// BuildRefundTransaction.
func CreateRefundTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, transactionHex string, privateKey string, servicePubKey string, feeRate int32, timeoutBlockHeight int32, destinationAddress string, lockupAddress string, cooperative bool, isTestnet bool) (string, error) {
txHex, err := BuildRefundTransaction(endpoint, id, claimLeaf, refundLeaf, transactionHex, privateKey, servicePubKey, feeRate, timeoutBlockHeight, destinationAddress, lockupAddress, cooperative, legacyNetworkName(isTestnet))
func CreateRefundTransaction(endpoint string, id string, claimLeaf string, refundLeaf string, transactionHex string, privateKey string, servicePubKey string, feeRate int32, minerFee int32, timeoutBlockHeight int32, destinationAddress string, lockupAddress string, cooperative bool, isTestnet bool) (string, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This adds minerFee to CreateRefundTransaction's signature, but the doc comment says this wrapper is kept for app builds that predate BuildRefundTransaction. The current Zeus app still calls it with the old 13 arguments:

  • ios/LncMobile/LncModule.mm:343 (LndmobileCreateRefundTransaction(..., feeRate, timeoutBlockHeight, ...))
  • android/app/src/main/java/com/zeus/lnc-rn/LncModule.kt:269 (Lndmobile.createRefundTransaction(...))

Both stop compiling once this build is bundled, unless they are updated at the same time. CreateReverseClaimTransaction keeps its signature, so the refund wrapper should do the same: drop the new parameter here and pass 0 for minerFee to BuildRefundTransaction, which keeps the fee-rate behaviour. Only BuildRefundTransaction needs the new argument.

@kaloudis
kaloudis merged commit af75a4a into ZeusLN:lnd-v0.21.4-beta-zeus.1 Oct 15, 2026
72 of 75 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.

2 participants