Menu
Lightning Intermediate 8 min read

Payment Routing

How Lightning finds payment paths through the network. Pathfinding algorithms, onion routing, and route optimization.

routing pathfinding onion gossip fees

Payment Routing

Lightning payments don’t require direct channels between sender and recipient. The network routes payments through intermediaries, with each hop forwarding the payment to the next.

Why Routing Matters for Agents

BenefitDescription
ConnectivityPay anyone reachable via network paths
PrivacyOnion routing hides payment details
CompetitionMultiple routes drive fees down
ResilienceFailed paths can be retried

Network Topology

The Lightning Network is a graph of nodes connected by channels:

        ┌─────┐
        │  D  │
       /       \
┌─────┐         ┌─────┐
│  A  │─────────│  C  │─────┐
└─────┘         └─────┘     │
    \           /           │
     \         /            │
      ┌─────┐              ┌─────┐
      │  B  │──────────────│  E  │
      └─────┘              └─────┘

Nodes: Lightning nodes (wallets, routing nodes) Edges: Payment channels with capacity

Pathfinding

The Problem

Given:

  • Source node (sender)
  • Destination node (recipient)
  • Amount to send

Find:

  • Path with sufficient liquidity
  • Minimizing fees and hops

Dijkstra’s Algorithm

Most implementations use Dijkstra’s shortest path algorithm weighted by:

cost = base_fee + (amount × fee_rate) + risk_factor

base_fee: Fixed cost per hop (typically 0-1000 msat) fee_rate: Proportional fee (typically 1-1000 ppm) risk_factor: Penalty for unreliable channels

Multi-Path Payments (MPP)

Large payments can be split across multiple routes:

Payment: 100,000 sats

Route 1: Alice → Carol → Bob (50,000 sats)
Route 2: Alice → Dave → Bob (30,000 sats)
Route 3: Alice → Eve → Bob (20,000 sats)

Benefits:

  • Use channels with limited liquidity
  • Better success rate for large payments
  • Privacy (observers see smaller amounts)

Onion Routing

Lightning uses onion routing to hide payment details from intermediaries.

Layered Encryption

┌──────────────────────────────────────────────┐
│ Carol's layer                                 │
│ ┌──────────────────────────────────────────┐ │
│ │ Dave's layer                              │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Eve's layer                          │ │ │
│ │ │ ┌──────────────────────────────────┐ │ │ │
│ │ │ │ Final destination (Bob)          │ │ │ │
│ │ │ └──────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘

Each node:

  1. Decrypts their layer
  2. Learns only the next hop
  3. Forwards the remaining onion

What Each Node Sees

NodeKnowsDoesn’t Know
SenderFull route, amount-
IntermediatePrevious hop, next hopOrigin, destination, amount
RecipientSender (sometimes), amountRoute taken

Onion Packet Structure

┌─────────────────────────────────────────┐
│ Version (1 byte)                        │
│ Public Key (33 bytes)                   │
│ Encrypted Routing Info (1300 bytes)     │
│ HMAC (32 bytes)                         │
└─────────────────────────────────────────┘

Total onion size: 1366 bytes (fits any route up to 20 hops)

Gossip Protocol

Nodes learn about the network through gossip—announcements propagated peer-to-peer.

Message Types

MessagePurpose
node_announcementNode info (alias, color, features)
channel_announcementNew channel created
channel_updateFee/policy changes

Channel Update Fields

channel_update:
  channel_id: 123456789
  timestamp: 1706745600
  flags: 0x01           # direction
  cltv_expiry_delta: 40
  htlc_minimum_msat: 1000
  fee_base_msat: 1000
  fee_proportional_millionths: 100
  htlc_maximum_msat: 1000000000

Fee Calculation

Routing fees are calculated per hop:

fee = base_fee_msat + (amount_msat × fee_rate_ppm / 1,000,000)

Example:

  • Amount: 100,000 sats (100,000,000 msat)
  • Base fee: 1000 msat (1 sat)
  • Fee rate: 100 ppm
fee = 1000 + (100,000,000 × 100 / 1,000,000)
fee = 1000 + 10,000
fee = 11,000 msat (11 sats)

Fee Accumulation

Fees accumulate backwards through the route:

Alice → Carol → Dave → Bob
        fee=10  fee=5

Bob receives: 100,000 sats
Dave forwards: 100,000 + 5 = 100,005 sats
Carol forwards: 100,005 + 10 = 100,015 sats
Alice sends: 100,015 sats

Route Hints

For private channels or mobile wallets, recipients provide route hints in invoices:

Invoice route hints:
  - Node: 03abc...
    Channel: 123456
    Fee base: 1000
    Fee rate: 100
    CLTV delta: 40

This tells the sender how to reach the recipient’s private node.

Pathfinding Strategies

Shortest Path

Minimize total fees:

weight = Σ(base_fee + amount × fee_rate)

Reliability-Weighted

Penalize unreliable channels:

weight = fee + (1 - success_probability) × penalty

Privacy-Optimized

Prefer paths that obscure payment patterns:

  • Avoid popular nodes
  • Use longer paths
  • Randomize route selection

Agent Implementation

Finding a Route

# Via LND
routes = lnd.query_routes(
    pub_key=destination_pubkey,
    amt=amount_sat,
    num_routes=3,
    fee_limit={"fixed": 1000}  # max 1000 sats
)

for route in routes.routes:
    print(f"Hops: {len(route.hops)}")
    print(f"Total fees: {route.total_fees_msat} msat")
    print(f"Total time lock: {route.total_time_lock} blocks")

Sending via Route

# Send with specific route
result = lnd.send_to_route(
    payment_hash=payment_hash,
    route=routes.routes[0]
)

if result.failure:
    print(f"Failed at hop {result.failure.failure_source_index}")
    print(f"Reason: {result.failure.code}")

Handling Failures

def send_with_retry(destination, amount, max_attempts=3):
    excluded_channels = []

    for attempt in range(max_attempts):
        routes = lnd.query_routes(
            pub_key=destination,
            amt=amount,
            ignored_pairs=excluded_channels
        )

        result = lnd.send_to_route(
            payment_hash=payment_hash,
            route=routes.routes[0]
        )

        if result.payment_preimage:
            return result  # Success

        # Add failed channel to exclusion list
        failed_hop = result.failure.failure_source_index
        failed_channel = routes.routes[0].hops[failed_hop].chan_id
        excluded_channels.append(failed_channel)

    raise Exception("Payment failed after retries")

Common Routing Failures

FailureCauseSolution
TEMPORARY_CHANNEL_FAILUREInsufficient liquidityTry different route
UNKNOWN_NEXT_PEERNode offlineExclude from route
FEE_INSUFFICIENTFees changedRecalculate route
INCORRECT_CLTV_EXPIRYTimelock mismatchUse updated channel info
CHANNEL_DISABLEDChannel inactiveExclude channel

Privacy Considerations

Timing Analysis

  • Payment timing can correlate sender/receiver
  • Mitigation: Random delays (not widely implemented)

Amount Correlation

  • Same amount at hops reveals route
  • Mitigation: MPP (splits into smaller amounts)

Node Correlation

  • Source node sees full route
  • Mitigation: Trampoline routing (delegate pathfinding)
  • HTLCs - How payments traverse routes
  • Channels - The edges in the network graph
  • Fees - Fee structures and optimization
  • BOLT-04 - Onion routing specification
  • BOLT-07 - Gossip protocol specification

Machine-Readable Summary

{
  "topic": "payment-routing",
  "key_concepts": [
    "pathfinding",
    "onion-routing",
    "gossip-protocol",
    "fee-calculation"
  ],
  "algorithms": [
    "dijkstra",
    "multi-path-payments"
  ],
  "privacy_features": [
    "layered-encryption",
    "intermediate-node-blindness"
  ]
}