commands: add list_channel_htlcs command to list failed, inflight and settled HTLCs for a channel
What changed, and why it matters
This commit adds a new read-only command that lets users list payment details (HTLCs) for one of their own Lightning channels. It does not change how money moves, does not add new network exposure, and does not appear to introduce a security vulnerability.
No security action required. This is a normal feature addition. As with any new command, ensure documentation and access-control expectations for wallet-local commands remain clear to users.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces list_channel_htlcs in electrum/commands.py. It is decorated with @command('wnl'), meaning it requires an open wallet and local access (not network). The command validates the channel belongs to the wallet, then iterates over existing channel payments and returns already-present HTLC metadata (ID, direction, amount, timestamp, payment hash) grouped by status. No state is modified; no secrets are exposed beyond what is already stored in the wallet database.
Changed components
electrum/commands.pyInspect captured patch +30 / −0
diff --git a/electrum/commands.py b/electrum/commands.py
index ac3143e..1291e8d 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -1990,6 +1990,36 @@ class Commands(Logger):
tx = chan.force_close_tx()
return tx.serialize()
+ @command('wnl')
+ async def list_channel_htlcs(self, channel_point, password=None, wallet: Abstract_Wallet = None):
+ """
+ return the settled, inflight and failed htlcs of a channel
+
+ arg:str:channel_point:Channel outpoint
+ """
+ txid, index = channel_point.split(':')
+ chan_id, _ = channel_id_from_funding_tx(txid, int(index))
+ if chan_id not in wallet.lnworker.channels:
+ raise UserFacingException(f'Unknown channel {channel_point}')
+ chan = wallet.lnworker.channels[chan_id]
+ folders = {
+ 'settled': [],
+ 'inflight': [],
+ 'failed': [],
+ }
+ for rhash, plist in chan.get_payments().items():
+ for htlc_with_status in plist:
+ if (fl := folders.get(htlc_with_status.status)) is None:
+ continue
+ fl.append({
+ 'id': htlc_with_status.htlc.htlc_id,
+ 'direction': 'OUT' if htlc_with_status.direction == SENT else 'IN',
+ 'amount': htlc_with_status.htlc.amount_msat,
+ 'timestamp': htlc_with_status.htlc.timestamp,
+ 'payment_hash': htlc_with_status.htlc.payment_hash.hex()
+ })
+ return folders
+
@command('wnl')
async def get_watchtower_ctn(self, channel_point, wallet: Abstract_Wallet = None):
"""
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.