pull down to refresh

The honest math before any tooling

Profit is not "fees earned." It is:

net = routing_fees + lease_income − rebalance_fees − swap_fees − amortized_chain_fees − capital_hurdle

The only metric worth optimizing is net_sats / (deployed_sats × days). Routing yield collapses to one identity: annual_yield_ppm ≈ earned_ppm × turnover, where turnover = routed_sats / deployed_sats per year. (Left-hand side in ppm, hence the next paragraph.)

That identity is brutal. A 1% yield at 200 ppm needs 50× turnover per year. Put 10M sats to work and earning 100k sats/year means routing ~500M sats/year — about 1.4M sats/day, i.e. fourteen 100k-sat forwards every single day. If each forwarded sat also costs ~100 ppm to circular-rebalance back, half the gross is gone and you are at 0.5%. Median small nodes land under 0.5%/yr; a well-run node with 5–20 channels on real corridors can reach 1–3%. Anyone quoting more is usually counting liquidity leases or has captive demand (their own shop). Price locked capital with a hurdle (3%+ if you would otherwise hold it), and remember that hosting alone can erase the yield: a $5/mo VPS at ~$50k/BTC is roughly 10k sats/month, which wipes out a 1% return on a 10M-sat node.

Ranked by impact: (1) route position, (2) turnover/capital efficiency, (3) two-sided fee pricing, (4) uptime, (5) rebalance discipline. Volume is never the goal.

Tooling: what is actually worth running

  • LNDg — yes. Auto-fees plus an auto-rebalancer with hard max-cost caps, HTLC-failure stream, channel scoring, watchtower management. The closest thing to set-and-forget for LND.
  • charge-lnd — yes, the fee-policy engine. Rule-based per-channel fees, with hysteresis and inbound (negative) fees on LND 0.18+.
  • Balance of Satoshis (bos) — yes, as the surgical CLI: rebalance and fee formulas, liquidity targets, node avoids. Not a full autopilot.
  • autofee — optional EMA-driven fees plus automatic negative-inbound discounts. It is a fee writer: run it or LNDg auto-fees or charge-lnd, never two at once.
  • ThunderHub / RTL — run one for visibility. They are eyes, not a brain; their rebalance buttons will happily overpay.
  • Lightning Terminal (Autofees / Autoloop / Pool) — use Pool to lease liquidity and Loop for swaps only when the quoted all-in cost passes your profit gate.
  • Boltz client — unattended submarine swaps; a real alternative to Loop, so compare the spread.
  • LN+ (lightningnetwork.plus) — free ring/triangle swaps, the cheapest source of balanced liquidity.
  • Amboss Magma / liquidity ads — buy or sell inbound when organic sourcing fails.
  • CLBOSS — full CLN autopilot; hands-off and hard to predict, only if you truly will not intervene.
  • circuitbreaker — worth running as griefing defence. It is a Core Lightning plugin configured through its own plugin options in the CLN config, not through charge-lnd.

Concrete automation

A charge-lnd config that does most of the work:

[default]
strategy = proportional
base_fee_msat = 1000
min_fee_ppm = 100
max_fee_ppm = 500
min_fee_ppm_delta = 25     # hysteresis: ignore changes under 25 ppm, stop gossip churn

[recover-open-cost]
chan.initiator = true
chan.min_capacity = 2000000
strategy = cost
cost_factor = 1.5          # price to recover 150% of open cost as the channel drains

[full-side]
chan.min_ratio = 0.9
strategy = static
base_fee_msat = 0
fee_ppm = 50               # cheap when we are full, to attract outflow

proportional sets the fee from the channel's balancedness: min_fee_ppm when local balance is low, rising toward max_fee_ppm as the channel fills. So the knob cuts both ways — set your min/max (or add a static rule for full channels) according to whether you want to discourage or encourage outflow. Always test with charge-lnd --dry-run -c charge-lnd.conf, and run only one fee writer.

Note on dead channels: disable is a valid charge-lnd strategy (it disables the outbound direction and reverses when the channel matches another policy). Use it for drained channels rather than leaving them priced as if they could sell.

bos for bounded, formula-driven actions:

# nudge one peer's price by formula (INBOUND in sats)
bos fees --to <pubkey> --set-fee-rate="IF(INBOUND>4000000,50,400)"

# keep a channel near 50:50, never paying more than 100 ppm, and skip
# counterparties that charge under 100 ppm
bos rebalance --out-target-inbound=capacity/2 --max-fee-rate 100 --max-fee 500 --avoid "fee_rate < 100/<PUBKEY>"

# surgical: refill one drained peer only
bos rebalance --amount 1000000 --in <drained_pubkey> --max-fee-rate 80 --max-fee 250

bos rebalance defaults to a 250 ppm max — set it explicitly lower on small nodes. Fee formulas (IF(...), capacity/2) come from the bos formula engine, and --avoid accepts directional expressions like the one above. Run it from cron at most hourly, after fee changes, and never concurrently with LNDg's auto-rebalancer.

Rebalancing economics

A self-rebalance shifts A sats from a low-value channel S to a high-value channel T. Approve it only if:

available_cost_ppm < ((earn_ppm_T − earn_ppm_S) × expected_turns) / safety_factor

Example: T earns 300 ppm, S earns 150 ppm, and the 1M sats should turn ~3 times before drifting back → (300−150)×3 = 450 ppm; divide by a 2× safety factor → cap the loop at ~225 ppm, and in practice 25–100 ppm. If T has zero forwards in the window, the cap is zero: fix price or peer selection first, do not refill dead capital. Preference order: natural circular flow → self-rebalance → swap (Boltz/Loop, pay spread plus chain fee) → force-close. The classic failure is paying 100–250 ppm to refill a channel that historically sells at 50 ppm.

Risk management

  • Keep anchor channels, cap the max commit fee, and open/close in cheap mempool windows.
  • Run a watchtower and maintain static channel backups, and test the restore. An SCB triggers a force-close and saves your balance; it is not a full-history backup.
  • Peer hygiene: reject peers with force-close history, under 30 channels, tiny capacity, or chronic disabling. Set a minimum channel size and a per-peer exposure cap.
  • Use circuitbreaker to cap concurrent and pending HTLCs and block HTLC-spam lockups.
  • Uptime above 99%, UPS plus SSD, and keep an on-chain reserve so you can always afford to close.

What I would do with a small node today

Minimum rational size is roughly 5M sats across 5–8 channels of 1–2M each. Below about 2–3M sats, pure routing is a hobby, not a yield — run a wallet with LSP inbound instead. Starting now: open 5–8 channels on real corridors, run charge-lnd in dry-run for 7 days, then enable it plus LNDg auto-rebalance with a hard ppm cap, use LN+ for free swaps, and retire any channel whose 60-day net is negative. Expect ~1%/yr and treat that as the cost of self-custody plus graph knowledge — the profit, when it exists, comes from a service attached to the node, not from the channels alone.