Core Lightning (CLN) Integration
Core Lightning (CLN, formerly c-lightning) — Lightning Network node by Blockstream, written in C + Python. If LND is "batteries included" with rich REST API, CLN is minimalist core with plugin architecture. CLN integration differs from LND: here Unix socket, JSON-RPC 2.0 and powerful plugin system. Let's see how this works in practice.
CLN Architecture: Plugins and RPC
CLN exposes Unix domain socket (default ~/.lightning/bitcoin/lightning-rpc) via JSON-RPC 2.0. No REST API built-in — either clnrest plugin or third-party proxy.
Plugins — CLN's key feature. Plugin is separate process (any language) talking to CLN via stdio. Plugins can:
- Add new RPC methods
- Subscribe to events (new payments, blocks, connections)
- Intercept hooks (pre-payment, peer connection)
- Modify node behavior
Fundamentally different from LND where extension only via gRPC.
Connecting to CLN
Direct JSON-RPC via Unix socket
import socket
import json
from pathlib import Path
class CLNSocket:
def __init__(self, socket_path: str = "~/.lightning/bitcoin/lightning-rpc"):
self.path = str(Path(socket_path).expanduser())
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(self.path)
self._id = 0
def call(self, method: str, params: dict | list = None) -> dict:
self._id += 1
request = {
"jsonrpc": "2.0",
"id": self._id,
"method": method,
"params": params or {},
}
data = json.dumps(request).encode()
# CLN uses newline-delimited JSON
self.sock.sendall(data + b"\n\n")
# Read response
buffer = b""
while True:
chunk = self.sock.recv(4096)
buffer += chunk
try:
response = json.loads(buffer)
if "error" in response:
raise CLNError(response["error"]["message"], response["error"]["code"])
return response["result"]
except json.JSONDecodeError:
continue # wait for more data
# Usage
cln = CLNSocket()
info = cln.call("getinfo")
print(f"Node ID: {info['id']}")
pyln-client: Official Python library
from pyln.client import LightningRpc
rpc = LightningRpc("/path/to/lightning-rpc")
# Node info
info = rpc.getinfo()
# Create invoice
invoice = rpc.invoice(
msatoshi=100000, # 100 sat in millisatoshi
label="order-12345", # unique label
description="Payment for order 12345",
expiry=3600, # 1 hour
)
print(invoice["bolt11"]) # BOLT11 invoice for QR code
print(invoice["payment_hash"])
# Wait for payment with timeout
result = rpc.waitinvoice(label="order-12345")
# result["status"] == "paid" or timeout
clnrest plugin: REST/WebSocket API
If HTTP API needed instead of Unix socket (e.g., for web app):
# Install clnrest plugin
pip install pyln-client pyln-testing
# Add to ~/.lightning/config
plugin=/path/to/clnrest.py
clnrest-port=3010
clnrest-host=127.0.0.1
clnrest-certs=/path/to/certs # TLS for production
After this, REST API available:
curl http://localhost:3010/v1/getinfo \
-H "Rune: YOUR_RUNE_TOKEN"
Rune — CLN authorization system (alternative to macaroons from LND). Create rune with limited permissions:
# Read-only rune for listinvoices + waitinvoice only
lightning-cli createrune restrictions='["method^listinvoices|method=waitinvoice"]'
Payment flow: Receiving payments
import asyncio
from pyln.client import LightningRpc
class CLNPaymentProcessor:
def __init__(self, rpc_path: str):
self.rpc = LightningRpc(rpc_path)
self.pending_invoices: dict[str, asyncio.Future] = {}
def create_invoice(self, amount_sat: int, order_id: str, description: str) -> dict:
label = f"order-{order_id}"
inv = self.rpc.invoice(
msatoshi=amount_sat * 1000,
label=label,
description=description,
expiry=900, # 15 minutes
)
return {
"bolt11": inv["bolt11"],
"payment_hash": inv["payment_hash"],
"expires_at": inv["expires_at"],
}
async def wait_for_payment(self, label: str, timeout: int = 900) -> bool:
"""Wait for invoice payment, return True on success"""
loop = asyncio.get_event_loop()
def blocking_wait():
try:
result = self.rpc.waitinvoice(label=label)
return result.get("status") == "paid"
except Exception:
return False
try:
paid = await asyncio.wait_for(
loop.run_in_executor(None, blocking_wait),
timeout=timeout
)
return paid
except asyncio.TimeoutError:
return False
Plugins: Extending CLN
Writing plugin — CLN's key superpower. Example minimal plugin logging all incoming payments:
#!/usr/bin/env python3
# payment_logger_plugin.py
from pyln.client import Plugin
plugin = Plugin()
@plugin.subscribe("invoice_payment")
def on_payment(invoice_payment, **kwargs):
"""Called on each successful incoming payment"""
label = invoice_payment.get("label")
amount_msat = invoice_payment.get("msat")
preimage = invoice_payment.get("preimage")
plugin.log(f"Payment received: label={label}, amount={amount_msat}msat")
# Here: webhook, DB insert, notification send
notify_webhook(label, amount_msat)
@plugin.method("my_custom_method")
def custom_method(plugin, some_param, **kwargs):
"""Add new RPC method to CLN"""
return {"result": f"Processed: {some_param}"}
plugin.run()
# Connect plugin (in ~/.lightning/config)
plugin=/path/to/payment_logger_plugin.py
Plugins via subscribe("invoice_payment") receive events without polling — right pattern for real-time payment processing.
Hook: Interceptor for payments
For advanced cases (rate limiting, fraud detection) — htlc_accepted hook:
@plugin.hook("htlc_accepted")
def on_htlc(onion, htlc, **kwargs):
"""Intercept incoming HTLC before acceptance"""
amount = htlc.get("amount_msat")
# Reject if too small (anti-spam)
if amount < 1000: # < 1 sat
return {"result": "fail", "failure_message": "4100"} # insufficient_fees
# Accept
return {"result": "continue"}
Routing and channel management
# Open channel
funding = rpc.fundchannel(
id="03abc...@ip:port",
amount=500000, # 500k sat
announce=True, # public channel
minconf=1, # minimum funding tx confirmations
)
# List channels with balances
channels = rpc.listpeerchannels()
for ch in channels["channels"]:
print(f"Channel {ch['short_channel_id']}: "
f"local={ch['to_us_msat']}msat, "
f"remote={ch['total_msat'] - ch['to_us_msat']}msat")
# Send payment
payment = rpc.pay(bolt11="lnbc...")
print(f"Status: {payment['status']}, preimage: {payment.get('payment_preimage')}")
CLN vs LND: Practical comparison
| Aspect | CLN | LND |
|---|---|---|
| API | Unix socket JSON-RPC, clnrest plugin | gRPC + REST built-in |
| Extensibility | Plugins (any language) | Interceptors (gRPC) |
| Performance | Lower RAM footprint | Higher at scale |
| Documentation | Fewer examples | Rich documentation |
| Macaroons/auth | Runes | Macaroons |
| Watch-only mode | No | Yes |
CLN preferred if: need custom plugins with non-standard logic, low footprint important, or already working with Blockstream infrastructure.
Basic integration development (payment receiving + webhook) — 3-5 days. Full plugin with custom routing logic — 1-2 weeks.







