From 35a8b647efe21f5e150c886f70575069c0eb61ad Mon Sep 17 00:00:00 2001 From: Harry Beckwith Date: Wed, 26 Aug 2026 14:01:34 -0700 Subject: [PATCH] Answer the four questions people actually ask about fees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four questions came in that the routing quest left implicit or unanswered: who receives the fees, does each hop add one, how do you know how many hops there will be, and does it matter. Three of them had no answer anywhere. A new stop takes all four in order, and the most interesting is the first: a routing node is never *sent* anything. It is offered slightly more on the channel a payment arrives on than it offers on the channel it forwards to, and when the preimage settles both HTLCs the difference simply stays on its side. Routing income is a gap between two numbers, not a transfer — which is why it arrives as channel balance and never as an on-chain payment. The hop-count answer has a pleasing asymmetry worth drawing out. The sender knows exactly, because the sender picks the route and builds the onion around it, so the total is settled before anything moves and there is no such thing as a surprise charge. Nobody on the route knows, because Quest #5's whole point is that a hop cannot learn the route's length or its own position. Both are true at once. Does it matter: less than people expect. A long cheap route beats a short expensive one and the wallet will take it; what matters is the total, and you see the total first. There is a ceiling of 20 hops, forced by the onion being a fixed 1,300 bytes, quoted from BOLT #4. All five questions are also in the question-first index, deep-linked to the stop that answers them, and the route finder now names who keeps each fee rather than just showing an amount. Co-Authored-By: Claude Opus 5 --- apps/web/src/runners/routing/route-finder.tsx | 14 ++++++- .../quests/src/lib/quest-ln-06-routing.ts | 42 +++++++++++++++++++ packages/quests/src/lib/questions.ts | 35 ++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/apps/web/src/runners/routing/route-finder.tsx b/apps/web/src/runners/routing/route-finder.tsx index 9ea2f7c..66f511d 100644 --- a/apps/web/src/runners/routing/route-finder.tsx +++ b/apps/web/src/runners/routing/route-finder.tsx @@ -163,7 +163,7 @@ export function RouteFinder({ finale }: RunnerProps) { {chosen && (
-
what each hop carries and charges
+
what each hop carries, and who keeps the difference
{chosen.hops.map((hop) => (
@@ -174,12 +174,22 @@ export function RouteFinder({ finale }: RunnerProps) { carries {fmt(hop.amountMsat)} {' '} - ← {hop.feeMsat === 0 ? 'no fee (the sender forwards out of it)' : `${hop.feeMsat.toLocaleString('en-US')} msat fee`} + ←{' '} + {hop.feeMsat === 0 + ? `${hop.from} charges nothing — it is the sender, forwarding for no one` + : `${hop.from} keeps ${hop.feeMsat.toLocaleString('en-US')} msat`}
))}
+
+ {chosen.hops.filter((h) => h.feeMsat > 0).length} fee + {chosen.hops.filter((h) => h.feeMsat > 0).length === 1 ? '' : 's'} across{' '} + {chosen.hops.length} channels — every forwarding node takes one, the sender and + the payee take none. Nothing is sent to them: each simply keeps the gap between + what arrives and what it passes on. +
A sends {fmt(chosen.totalSendMsat)} so that C receives{' '} {amountSats.toLocaleString('en-US')} sats — fees never come out of the invoice diff --git a/packages/quests/src/lib/quest-ln-06-routing.ts b/packages/quests/src/lib/quest-ln-06-routing.ts index 4618373..24a1a79 100644 --- a/packages/quests/src/lib/quest-ln-06-routing.ts +++ b/packages/quests/src/lib/quest-ln-06-routing.ts @@ -142,6 +142,48 @@ export const questLn06: Quest = { ], }, }, + { + id: 'who-gets-paid', + title: 'Who actually receives a fee, and how many of them are there?', + takeaway: + "Every **forwarding** node takes one, and nobody else: not the sender, who is forwarding for no one, and not the payee, who is being paid. So a route through two intermediates has exactly two fees — and the sender knows that number before sending, because the sender chose the route.", + annotationsOpen: true, + quiz: [ + { + question: 'How does the hub actually receive its fee?', + options: [ + 'The sender makes a second, separate payment to it', + 'It keeps the difference: more arrives on its incoming channel than it sends on its outgoing one', + 'It deducts the fee from the amount the payee receives', + ], + answer: 1, + explain: + "There is no second payment and nothing is deducted from the payee. The hub is offered 1,002.2 sats on one channel and offers 1,000 sats on the other; when the preimage settles both, the 2.2 sat difference is simply left on the hub's side of its channels. The fee is a gap between two numbers, not a transfer — which is why routing income arrives as channel balance and never as an on-chain payment.", + }, + ], + prose: [ + "**Who receives them.** Each forwarding node keeps the fee for its own forward, and here is the part that surprises people: it is never paid as a separate transaction. The hub is offered slightly more on the channel the payment arrives on than it offers on the channel it sends out of, and when the preimage comes back and settles both HTLCs, that difference simply stays on its side. A routing node's income is a gap between two numbers. Nothing is ever sent to it.", + "**Does each hop add a fee.** Every hop that *forwards* does. Two hops do not: the sender, who is not forwarding on anyone's behalf, and the recipient, who is being paid rather than passing anything on. So a route across N channels carries N−1 fees. In the A→B→C example that is exactly one — B's — which is why the total came to 10,199 rather than something doubled.", + "**How do you know how many hops there will be.** *You* know exactly, because you chose them. Route selection happens entirely in the sender's wallet: it picks the path, adds up the fees, and builds the onion around that decision (Quest #5). The number is settled before a single satoshi moves, and your wallet shows you the total in advance — which is why Lightning has no equivalent of a surprise charge.", + "Everyone else is in the dark, and deliberately so. Quest #5's guarantee was that a hop cannot learn the route's length or its own position in it. Both facts are true at once: the sender knows the hop count precisely, and nobody on the route does. The fee total is public to exactly one person — the one paying it.", + "**Does it matter.** Less than you would guess. More hops means more fees and more timelock, but a four-hop route of nodes charging nothing routinely beats a two-hop route through someone expensive, and your wallet will take it. What matters is the total, not the count, and you see the total first. There is a hard ceiling — the onion is a fixed 1,300 bytes, so a route tops out at **20 hops** — but real routes are rarely more than four or five, because each extra hop is another chance for something to be offline.", + ], + annotations: [ + { lines: 'L1088–89', text: 'The ceiling, stated in passing: 20 hops, forced by the onion being a fixed size. Real routes are far shorter.' }, + ], + excerpt: { + pin: BOLTS_PIN, + ref: { file: '04-onion-routing.md', startLine: 1086, endLine: 1090 }, + language: 'text', + lines: [ + { n: 1086, text: 'of 3 represents 300 ms. Nodes along the path that lack accurate timing information may report a value of zero.' }, + { n: 1087, text: '' }, + { n: 1088, text: 'The erring node puts its hold time at the start of this array and zeroes out the rest. The size of the field is based on' }, + { n: 1089, text: 'the maximum supported number of hops in a route (20).', highlight: true }, + { n: 1090, text: '' }, + ], + }, + }, { id: 'choosing', title: 'Choosing a path, and why it sometimes fails', diff --git a/packages/quests/src/lib/questions.ts b/packages/quests/src/lib/questions.ts index efbd340..1de4c15 100644 --- a/packages/quests/src/lib/questions.ts +++ b/packages/quests/src/lib/questions.ts @@ -342,6 +342,41 @@ export const questions: NewbieQuestion[] = [ slug: 'what-is-an-invoice', stop: 'the-hash', }, + { + site: 'lightning', + question: 'Who actually receives the routing fees?', + short: 'Each forwarding node keeps its own — and never as a separate payment. It is simply offered more on one channel than it sends on the other, and keeps the difference.', + slug: 'finding-a-route', + stop: 'who-gets-paid', + }, + { + site: 'lightning', + question: 'Does every hop add a fee?', + short: 'Every hop that forwards does. The sender does not (it forwards for nobody) and the payee does not (it is being paid), so N channels means N−1 fees.', + slug: 'finding-a-route', + stop: 'who-gets-paid', + }, + { + site: 'lightning', + question: 'How do I know how many hops my payment will take?', + short: 'You choose them — route selection happens in your own wallet, so the count and the total cost are settled before anything moves. Nobody on the route knows the length.', + slug: 'finding-a-route', + stop: 'who-gets-paid', + }, + { + site: 'lightning', + question: 'Does the number of hops matter?', + short: 'Less than you would think. A long cheap route beats a short expensive one, and you see the total in advance. The onion caps a route at 20 hops; real ones are far shorter.', + slug: 'finding-a-route', + stop: 'who-gets-paid', + }, + { + site: 'lightning', + question: 'Who decides what a Lightning payment costs?', + short: 'Nobody centrally. Each node picks two numbers, advertises them, and your wallet adds up the cheapest path. The specification fixes the formula, never the numbers.', + slug: 'finding-a-route', + stop: 'setting-fees', + }, ]; /** The question-first index for one front door. */