L402 — Lightning Network
Pay for oracle attestations with Lightning sats. Native Bitcoin integration for wallets, nodes, and Lightning-enabled applications.
L402 (formerly LSAT) is an HTTP payment protocol where the server returns a 402 with a Lightning invoice and a macaroon. The client pays the invoice, extracts the preimage, and retries the request with the macaroon + preimage as proof of payment. The oracle responds with a secp256k1 ECDSA-signed attestation.
How It Works
Request an oracle endpoint
Send a GET request to any paid endpoint. The L402 proxy listens on the same API domain.
curl -i https://api.myceliasignal.com/oracle/btcusd
Receive 402 with invoice and macaroon
The server responds with HTTP 402. The WWW-Authenticate header contains the L402 challenge with a base64-encoded macaroon and a BOLT 11 Lightning invoice.
HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgELbXljZWxpYS...", invoice="lnbc100n1pj..."
The macaroon encodes the payment hash as its identifier. The invoice amount is the price in sats for that endpoint.
Pay the Lightning invoice
Pay the BOLT 11 invoice using any Lightning wallet or node. Save the preimage from the payment receipt — this is your proof of payment.
You must extract and save the preimage (also called payment secret or payment proof). Without it, you cannot authenticate. Most Lightning libraries and wallets expose this in the payment response.
# Pay the invoice and extract preimage
lncli payinvoice --json lnbc100n1pj... | jq -r '.payment_preimage'
Retry with L402 credentials
Resend the original request with the Authorization header containing the macaroon and preimage, separated by a colon.
curl https://api.myceliasignal.com/oracle/btcusd \ -H "Authorization: L402 <macaroon_base64>:<preimage_hex>"
The proxy decodes the macaroon, extracts the payment hash from its ID, and queries LND to confirm the invoice has been settled.
Receive secp256k1-signed attestation
On successful payment verification, the L402 proxy forwards the request to the oracle backend and returns the response directly. The signature is secp256k1 ECDSA from the backend.
{
"domain": "BTCUSD",
"canonical": "v1|BTCUSD|84231.50|USD|2|2026-02-28T07:51:00Z|890123|binance,...|median",
"signature": "<base64-secp256k1-ecdsa-signature>",
"pubkey": "<compressed-hex-secp256k1-pubkey>"
}
L402 responses come directly from the oracle backend with secp256k1 ECDSA signatures. x402 responses are re-signed by the x402 proxy with Ed25519. Both sign the same canonical string format. The L402 response does not include a signing_scheme field — if this field is absent, it's secp256k1.
Programmatic Integration (Python)
import httpx, re, hashlib, base64 from ecdsa import VerifyingKey, SECP256k1 def parse_l402_challenge(www_auth: str) -> tuple[str, str]: """Parse macaroon and invoice from WWW-Authenticate header.""" mac = re.search(r'macaroon="([^"]+)"', www_auth).group(1) inv = re.search(r'invoice="([^"]+)"', www_auth).group(1) return mac, inv def query_oracle_l402(pair: str, pay_invoice_fn) -> dict: """Query oracle via L402. pay_invoice_fn should accept an invoice string and return the preimage hex.""" url = f"https://api.myceliasignal.com/oracle/{pair}" with httpx.Client(timeout=15) as client: # Step 1: Get 402 challenge resp = client.get(url) if resp.status_code != 402: raise Exception(f"Expected 402, got {resp.status_code}") www_auth = resp.headers["WWW-Authenticate"] macaroon, invoice = parse_l402_challenge(www_auth) # Step 2: Pay invoice (your Lightning implementation) preimage = pay_invoice_fn(invoice) # Step 3: Retry with L402 credentials resp = client.get( url, headers={"Authorization": f"L402 {macaroon}:{preimage}"} ) if resp.status_code != 200: raise Exception(f"Auth failed: {resp.text}") data = resp.json() # Step 4: Verify secp256k1 signature msg_hash = hashlib.sha256(data["canonical"].encode()).digest() vk = VerifyingKey.from_string( bytes.fromhex(data["pubkey"]), curve=SECP256k1 ) vk.verify_digest( base64.b64decode(data["signature"]), msg_hash ) # Parse price from canonical fields = data["canonical"].split("|") return { "price": fields[2], "sources": fields[7].split(","), "timestamp": fields[5], "verified": True, }
Pricing
| Endpoint Type | Lightning Amount |
|---|---|
| Spot pairs (BTC/USD, ETH/USD, etc.) | 10 sats |
| VWAP pairs (BTC/USD VWAP, BTC/EUR VWAP) | 20 sats |
Authentication Details
Macaroon Structure
The macaroon is minted by the L402 proxy using the payment hash from the Lightning invoice as its identifier. When you present the macaroon + preimage, the proxy:
1. Decodes the macaroon and extracts the payment hash from its ID.
2. Queries LND's REST API to verify the invoice with that payment hash has been settled.
3. If settled, proxies the request to the oracle backend and returns the response.
Each macaroon + preimage pair corresponds to a single Lightning invoice. Once used, you'll need to request a new invoice (new 402 challenge) for subsequent queries. There is no session or token reuse.
Authorization Header Format
Authorization: L402 <macaroon_base64>:<preimage_hex>
The macaroon is base64-encoded (as received in the WWW-Authenticate header). The preimage is hex-encoded (as returned by most Lightning implementations).
Lightning Node Requirements
To use L402, you need the ability to pay Lightning invoices programmatically. Common options:
| Implementation | Language | Notes |
|---|---|---|
LND (lncli / gRPC / REST) | Go / any | Most common. Use sendpayment and extract payment_preimage |
CLN (lightning-cli) | C / any | Use pay command, preimage in response |
| LDK | Rust | Embedded Lightning node library |
| Breez SDK | Dart / Kotlin / Swift | Mobile-friendly, non-custodial |
| LNbits / Alby | REST API | Custodial — simpler but trust tradeoff |
L402 Protocol Details
| Parameter | Value |
|---|---|
| Protocol | L402 (formerly LSAT) |
| Invoice format | BOLT 11 |
| Macaroon library | gopkg.in/macaroon.v2 |
| LND backend | REST API on localhost |
| Signing scheme | secp256k1 ECDSA (SHA-256 pre-hashed) |
| Public key format | Compressed, 33-byte hex |
Choosing Between L402 and x402
| L402 (Lightning) | x402 (USDC on Base) | |
|---|---|---|
| Best for | Bitcoin-native apps, Lightning wallets | AI agents, EVM apps, stablecoin workflows |
| Payment speed | Instant (Lightning) | ~2 seconds (Base L2 confirmation) |
| Currency risk | BTC-denominated (sat price stable in sats) | USD-denominated (depeg risk mitigated by circuit breaker) |
| Signature | secp256k1 ECDSA | Ed25519 |
| Infrastructure | Requires Lightning node or wallet | Requires Base wallet with USDC |
| Depeg immunity | Yes — sats are sats | No — circuit breaker suspends if USDC depegs |
Both protocols return the same oracle data with the same canonical format. You can implement both and fall back from one to the other if either payment rail is disrupted.