L402 — Lightning Network

Pay for oracle attestations with Lightning sats. Native Bitcoin integration for wallets, nodes, and Lightning-enabled applications.

Overview

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

1

Request an oracle endpoint

Send a GET request to any paid endpoint. The L402 proxy listens on the same API domain.

bash
curl -i https://api.myceliasignal.com/oracle/btcusd
2

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 — response header
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.

3

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.

Critical

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.

bash — lncli example
# Pay the invoice and extract preimage
lncli payinvoice --json lnbc100n1pj... | jq -r '.payment_preimage'
4

Retry with L402 credentials

Resend the original request with the Authorization header containing the macaroon and preimage, separated by a colon.

bash
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.

5

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.

json — success response
{
  "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 vs x402 responses

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)

python — pip install httpx ecdsa
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 TypeLightning 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.

One-time use

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

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:

ImplementationLanguageNotes
LND (lncli / gRPC / REST)Go / anyMost common. Use sendpayment and extract payment_preimage
CLN (lightning-cli)C / anyUse pay command, preimage in response
LDKRustEmbedded Lightning node library
Breez SDKDart / Kotlin / SwiftMobile-friendly, non-custodial
LNbits / AlbyREST APICustodial — simpler but trust tradeoff

L402 Protocol Details

ParameterValue
ProtocolL402 (formerly LSAT)
Invoice formatBOLT 11
Macaroon librarygopkg.in/macaroon.v2
LND backendREST API on localhost
Signing schemesecp256k1 ECDSA (SHA-256 pre-hashed)
Public key formatCompressed, 33-byte hex

Choosing Between L402 and x402

L402 (Lightning)x402 (USDC on Base)
Best forBitcoin-native apps, Lightning walletsAI agents, EVM apps, stablecoin workflows
Payment speedInstant (Lightning)~2 seconds (Base L2 confirmation)
Currency riskBTC-denominated (sat price stable in sats)USD-denominated (depeg risk mitigated by circuit breaker)
Signaturesecp256k1 ECDSAEd25519
InfrastructureRequires Lightning node or walletRequires Base wallet with USDC
Depeg immunityYes — sats are satsNo — circuit breaker suspends if USDC depegs
Tip

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.