Commit Graph

264 Commits

Author SHA1 Message Date
f321x 306cac192b lnaddr: rename LnAddr -> bolt11
The LnAddr, lndecode and lnencode naming didn't imply that it is
bolt 11 specific, making it confusing to work with, now that there are
also bolt 12 "lnaddr".
Renaming it to *bolt11* creates a clear separation to bolt 12 things and
reduces mental load.

This commit is pure renaming (using the PyCharm IDE refactor function),
except for the removal of the `object` inheritance of LnAddr/BOLT11Addr,
this is Python 2 legacy.
2026-04-27 16:28:19 +02:00
f321x f56e6318e3 swaps: make SwapManager.percentage Decimal
If SwapManager.percentage was a 0.2 float, rounding differences would
cause an exception in the fee calculation inverse sanity check when entering 20
000 sats into the SwapDialog. By making self.percentage a decimal we can
prevent this kind of issue.

```
File "/home/user/code/vibecoding_vm/electrum/electrum/gui/qt/swap_dialog.py", line 294, in on_send_edited
    recv_amount = self.swap_manager.get_recv_amount(send_amount, is_reverse=self.is_reverse)
  File "/home/user/code/vibecoding_vm/electrum/electrum/submarine_swaps.py", line 1320, in get_recv_amount
    if abs(send_amount - inverted_send_amount) > 1:
           ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
```
2026-03-19 09:17:34 +01:00
f321x 88c7a731c0 swaps: rm until filter when fetching server pairs
The `until` filter would limit the relay to only send us events created
up until this timestamp. If the user opens a swap transport by opening
the swap dialog, and keeps the dialog open the dialog will naturally age
above this limit and the relay will stop sending the client swapserver
events as they have (legitimately) been created after this timestamp.

As sanity check we still have the comparison against the current
timestamp in the event parsing loop to prevent pre/backdating.

Fixes https://github.com/spesmilo/electrum/issues/10520
2026-03-16 12:20:47 +01:00
SomberNight 0b2c7a8a38 lnsweep: safer maybe_reveal_preimage_for_htlc, add "is_preimage_public"
"When should we reveal preimages onchain?"
This commit tries to simplify the thinking by making the observation:
- we can reveal preimages (actually in any context) if they are already public
- a preimage is public if any other lightning node knows it besides us
  - if we learn the preimage from another LN node, it is public
  - if we send update_fulfill_htlc, it becomes public
  - if we see a preimage onchain, it is public

- in lnsweep._maybe_reveal_preimage_for_htlc:
  - partial mpp check is not relevant if preimage is already public
  - let's just always do KeepWatchingTXO, for sanity/safety

Co-authored-by: ThomasV <thomasv@electrum.org>
2026-02-24 17:49:15 +00:00
f321x 923d48f9db lnworker: differentiate PaymentInfo by direction
Allows storing two different payment info of the same payment hash by
including the direction into the db key.
We create and store PaymentInfo for sending attempts and for requests (receiving),
if we try to pay ourself (e.g. through a channel rebalance) the checks
in `save_payment_info` would prevent this and throw an exception.
By storing the PaymentInfos of outgoing and incoming payments separately in
the db this collision is avoided and it makes it easier to reason about
which PaymentInfo belongs where.
2025-12-01 18:39:56 +01:00
ghost43 3f45c41981 Merge pull request #10230 from f321x/refactor_htlc_handling
lightning: refactor htlc switch
2025-11-27 17:26:53 +00:00
f321x 0f314d1dd9 lnpeer/lnworker: refactor htlc_switch
refactor `htlc_switch` to new architecture to make it more robust
against partial settlement of htlc sets and increase maintainability.
Htlcs are now processed in two steps, first the htlcs are collected into
sets from the channels, and potentially failed on their own already.
Then a second loop iterates over the htlc sets and finalizes only on
whole sets.

# Conflicts:
#	electrum/lnpeer.py
2025-11-27 17:57:14 +01:00
f321x 3d0ba33652 swaps: followup 10303
small followup replacing tx height integers with const variables and
considering claim tx broadcast too if there is an unconfirmed parent
(height -1).
2025-11-27 17:44:49 +01:00
f321x 8a70fdcb81 qt: expose swaps to address as Submarine Payments
Exposes reverse submarine swaps to an external/specific address in the
TxEditor gui as "Submarine Payments". The user can enter a onchain
address in the Send Tab and then pay it from the lightning balance in
the send tab by enabling the Submarine Payments option in the TxEditor
dialog menu and switching to the Submarine Payment tab in the Tab bar.
2025-11-27 11:17:53 +01:00
f321x a0455f8382 swaps: allow reverse swaps to external address
Implement logic to claim a reverse swap funding output to any given
address. This allows to do onchain payments to external recipients
through a submarine swap.
2025-11-27 09:50:04 +01:00
ThomasV 879dcb3224 add dust_override to SweepInfo
We might want to set this value independently from is_anchor.
2025-11-03 14:07:58 +01:00
f321x 286fc4b86e lnworker: enforce creation of PaymentInfo for b11
Enforce that the information used to create a bolt11 invoice using
`get_bolt11_invoice()` is similar to the related instance of PaymentInfo
by requiring a PaymentInfo as argument for `get_bolt11_invoice()`.
This way the invoice cannot differ from the created PaymentInfo.
This allows to use the information in PaymentInfo for validation of
incoming htlcs more reliably.

To cover all required information for the creation of a b11 invoice the
PaymentInfo class has to be extended with a expiry and
min_final_cltv_expiry. This requires a db upgrade.
2025-09-30 09:54:35 +02:00
SomberNight b944371ffd adb: change API of util.TxMinedInfo: height() is now always SPV-ed 2025-09-24 13:46:24 +00:00
ghost43 b4a6de46f4 Merge pull request #10188 from f321x/capacity_warning_zero_amount
qt: receive dialog: also propose swaps for 0 amount invoices
2025-09-09 16:16:23 +00:00
f321x e1de1111f8 swaps: only set swap redeemed if preimage is available
For forward swaps this will ensure that the swap only gets set redeemed
and cleaned up after the preimage has been extracted, as it could happen
that `current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY`
is true even if the preimage has not been extracted yet.

For reverse swaps this changes nothing as they always have the preimage.
2025-09-08 17:25:49 +02:00
f321x 2c67c5e1e5 swaps: make min swap amount a const var
instead of hardcoding 20_000 sat directly in the code, make
MIN_SWAP_AMOUNT_SAT a const variable outside of SwapManager so it can be
used for other code too.
2025-09-01 10:09:33 +02:00
SomberNight 835b04d5c6 swaps: add explicit check that (onchain_locktime < LN_locktime)
This was already implicitly checked. This diff makes the check explicit, and serves as an additional sanity-check.
- for client-forward-swaps, we have
  - "cltv safety requirement: (onchain_locktime < LN_locktime),   otherwise client is vulnerable"
  - server chooses onchain locktime delta = 70
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/submarine_swaps.py#L701
  - client checks that onchain locktime delta is <100
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/submarine_swaps.py#L887
  - client chooses LN locktime delta = 432
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/submarine_swaps.py#L907
- for client-reverse-swaps, we have
  - "cltv safety requirement: (onchain_locktime < LN_locktime),   otherwise server is vulnerable"
  - server chooses onchain locktime delta = 70
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/submarine_swaps.py#L598
  - server chooses LN locktime delta: unset, i.e. our default of 147
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/submarine_swaps.py#L612
    https://github.com/spesmilo/electrum/blob/71255c1e735cdfcd881e045b9f2f6bc6b599f459/electrum/lnworker.py#L2273
2025-08-26 04:55:10 +00:00
SomberNight 7989d6467b swaps: trivial rename WITNESS_TEMPLATE_REVERSE_SWAP 2025-08-23 12:47:53 +00:00
SomberNight 08f041f3da swaps: add check for blockchain().is_tip_stale() 2025-08-23 02:41:53 +00:00
SomberNight a7afd59dec swaps: more clean-up, add comments, more sanity checks 2025-08-23 02:41:43 +00:00
SomberNight fc362826e8 swaps: clean-up onchain script construction 2025-08-23 02:41:40 +00:00
ThomasV 44a95bafbe Merge pull request #10164 from f321x/proper_cleanup_after_swap_failed
swaps: cleanup data after swap gets failed
2025-08-22 15:20:47 +02:00
f321x 97a0d27fa6 swaps: cleanup after successful swap
unregister hold invoice callback and delete payment bundle. they are not
used anymore.
2025-08-22 12:52:43 +02:00
f321x c623cca654 swaps: cleanup data after swap gets failed
Removes the persisted payment info from lnworker once a swap got failed.
Stops persisting the OnionRoutingFailure as it is sufficient to delete
the payment info to fail potential incoming htlcs.
Deletes stored swap leftovers in lnworker and SwapManager
2025-08-21 19:56:42 +02:00
ThomasV 864932c79a reverse swaps: in the CLI, replace 'server_mining_fee' with
'prepayment', which corresponds to the trusted part of the
lightning payment.

We use 2*sm.mining_fee, where 'mining_fee' is the flat part of
the server fee. However, future protocol should probably allow
to set a value that does not depend on 'mining_fee'.
(note that LND uses a hardcoded amount).
2025-08-21 19:47:00 +02:00
f321x aa661da9e2 swaps: stop sending whole req exception to client 2025-08-21 14:41:47 +02:00
f321x 9086f1af14 swaps: rate limit swapserver requests 2025-08-21 14:41:36 +02:00
ghost43 e85a3f2d3f Merge pull request #10157 from SomberNight/202508_swap_sanity_check_costs
swaps: add sanity-check for total swap costs
2025-08-20 18:05:13 +00:00
f321x 71c71a96f3 swaps: add sanity check to reverse swap mining fee 2025-08-20 17:57:36 +02:00
SomberNight bb39ca5595 swaps: add sanity-check for total swap costs 2025-08-20 15:13:25 +00:00
ThomasV 5553d5aa86 Merge pull request #10152 from SomberNight/202508_swaps_dm_replies
swaps: nostr: add sanity checks for replies
2025-08-20 12:37:41 +02:00
f321x 04b0aca878 swaps: improve prepayment invoice handling 2025-08-20 12:04:44 +02:00
SomberNight 93e0e8a7b8 swaps: nostr: add sanity checks for replies 2025-08-19 16:38:27 +00:00
ThomasV eae6ddd773 submarine_swaps: use dict instead of defaultdict for dm_replies 2025-08-19 17:54:25 +02:00
SomberNight 81be0554a3 swaps: more robust parsing 2025-08-19 13:42:21 +00:00
f321x 37614e9092 swaps: handle timeouts in send_direct_message
Adds logic to retry sending a direct message in
`NostrTransport.send_direct_message()` on `TimeoutError`.
Handles `TimeoutError` exception more gracefully by catching it and
returning `None`.
2025-08-19 15:29:58 +02:00
f321x a68bfab596 swaps: improve preimage extraction logic 2025-08-18 10:03:24 +02:00
ThomasV 971e3f1945 submarine_swaps: fix swapserver dying taskgroup (follow-up 21e3fd91dd and 4490bd3a76) 2025-08-08 16:17:01 +02:00
f321x dd41e87295 fix: NostrTransport.update_relays() KeyError
`NostrTransport.update_relays()` raises a `KeyError` when the offer of
the configured swapserver (`config.SWAPSERVER_NPUB`) is not in
`self._offers` even though `sm.pairs_updated` gets triggered. This
happens because `NostrTransport.get_pairs()` called `sm.update_pairs()`
before adding the received offer to `self._offers`.
2025-07-18 10:17:12 +02:00
SomberNight f2f1dddcc8 swaps: factor out pubkey_to_rgb_color into core lib 2025-07-14 21:09:18 +00:00
SomberNight f337b4782d lnwatcher: keep watching sweep TXOs that are dust due to high fees
- if fee estimates are high atm, some outputs are not worth to sweep
- however, fee estimates might be only-temporarily very high
  - previously in such a case lnwatcher would just discard outputs as dust,
    and mark the channel REDEEMED (and hence never watch it or try again)
  - now, instead, if the outputs would not be dust if fee estimates were lower,
    lnwatcher will keep watching the channel
    - and if estimates go down, lnwatcher will sweep them then
- relatedly, previously txbatcher.is_dust() used allow_fallback_to_static_rates=True,
    and it erroneously almost always fell back to the static rates (150 s/b) during
	startup (race: lnwatcher was faster than the network managed to get estimates)
	- now, instead, txbatcher.is_dust() does not fallback to static rates,
	  and the callers are supposed to handle NoDynamicFeeEstimates.
	  - I think this makes much more sense. The previous meaning of "is_dust"
	    with the fallback was weird. Now it means: "is dust at current feerates".

fixes https://github.com/spesmilo/electrum/issues/9980
2025-07-08 14:02:52 +00:00
f321x 73249404d4 qt: SwapDialog: update on changes, don't translate fstr
and differenciate between a single 'provider' and multiple or 0
'providers'. Also update SwapDialog and SwapServerDialog
when new offers are incoming. Stops translating f-string.
2025-06-27 11:18:38 +02:00
Sander van Grieken 1687b17e00 submarine_swaps: wait for unlock if wallet is password protected before starting nostr swap service 2025-06-16 09:39:50 +02:00
ThomasV 213d182f30 Merge pull request #9933 from f321x/prevent_fail_swap_exception
fix: prevent KeyError if _fail_swap gets called multiple times
2025-06-11 13:09:52 +02:00
f321x 8b15d64dc9 fix: prevent KeyError if _fail_swap gets called multiple times
If `_fail_swap()` gets called multiple times (e.g. from callbacks) this
would race a `KeyError` as the swap got already popped from
`self._swaps`.
In theory `_fail_swap` unregisters itself from the lnwatcher callback
but the callback may is scheduled multiple times before it has the
chance to unregister itself.
2025-06-11 11:37:24 +02:00
SomberNight 47e76b25b1 swaps: add some type hints, and force kwargs 2025-06-10 16:20:43 +00:00
SomberNight 2fb4debc7c commands: fix normal_swap cmd 2025-06-10 16:20:39 +00:00
ThomasV 86f3eec8e6 fix user-facing exception refreshing history after swap is refunded
(txbatcher first adds the tx unsigned)
2025-06-10 10:44:10 +02:00
f321x 37181cd3a8 disable qt swap fee slider on reverse swaps, change eta
disables the fee slider in the swap dialog for reverse swaps as the tx
fee for claiming is not configurable by the user. Also replaces calls to
`sm.get_swap_tx_fee()` with `sm.get_fee_for_txbatcher()` as this is the
correct fee estimate for claim transactions, instead of the config fee
eta used by `get_swap_tx_fee()`.
2025-06-09 17:36:38 +02:00
f321x 3a39abc415 fix: use single long lived transport in qeswaphelper
changes qeswaphelper to shate a single, long lived transport instance
instead of opening new transports to do swaps and fetch offers.
This allows to continuosly fetch offers, so events which get returned
later by slow relays don't get missed and the fee values stay updated.
Also fixes a race causing the list to miss some swapservers, as the
current implementation fetches only until
`swap_manager.is_initialized()` is set, which will get set as soon as an
event of the configured swapserver is received. So if the event of the
configured swapserver is received as first, all server events coming in
after it would get ignored.
2025-06-06 14:38:11 +02:00