Skip to main content
Glama

pay_l402_challenge

Pay Lightning invoices to obtain L402 or MPP authorization tokens for API access and transactions.

Instructions

Manually pay an L402 or MPP invoice and receive the authorization token. Use this if you need to handle the L402/MPP flow yourself. Omit macaroon for MPP (Machine Payments Protocol) mode.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
invoiceYesBOLT11 Lightning invoice string
macaroonNoBase64-encoded macaroon from the L402 challenge. Omit for MPP mode (preimage-only authentication).
max_satsNoMaximum satoshis allowed for this payment

Implementation Reference

  • The core implementation of the pay_l402_challenge tool, responsible for paying a Lightning invoice and returning an L402 or MPP token.
    async def pay_l402_challenge(
        invoice: str,
        macaroon: str | None = None,
        max_sats: int = 1000,
        wallet: "NWCWallet | None" = None,
        budget_manager: "BudgetManager | None" = None,
    ) -> str:
        """
        Manually pay an L402 or MPP invoice and receive the authorization token.
    
        This is useful when you want to handle the L402/MPP flow yourself rather than
        using access_l402_resource which does it automatically.
    
        When macaroon is provided, uses L402 protocol.
        When macaroon is omitted, uses MPP (Machine Payments Protocol) — preimage only.
    
        Args:
            invoice: BOLT11 Lightning invoice string
            macaroon: Base64-encoded macaroon from the L402 challenge (optional; omit for MPP mode)
            max_sats: Maximum satoshis allowed for this payment
            wallet: NWC wallet instance
            budget_manager: Budget manager for tracking spending
    
        Returns:
            JSON with L402/MPP token or error message
        """
        if not wallet:
            return json.dumps(
                {"success": False, "error": "Wallet not initialized. Check NWC connection."}
            )
    
        if not invoice:
            return json.dumps({"success": False, "error": "Invoice is required"})
    
        # Normalize invoice: strip whitespace/newlines that could cause decode or payment failures
        invoice = invoice.strip()
    
        # Normalize macaroon: strip whitespace and treat empty/whitespace-only as None
        if macaroon is not None:
            macaroon = macaroon.strip()
            if not macaroon:
                macaroon = None
        is_mpp = macaroon is None
    
        try:
            # Parse invoice to get amount
            decoded = decode_bolt11(invoice)
            amount_msat = None
            amount_sats = None
    
            if hasattr(decoded, "amount_msat") and decoded.amount_msat:
                amount_msat = decoded.amount_msat
                amount_sats = -(-amount_msat // 1000)  # ceil division: sub-sat amounts round up to 1
            elif hasattr(decoded, "amount") and decoded.amount:
                amount_sats = decoded.amount
    
            # Reject no-amount invoices (security: could bypass budget checks)
            if amount_sats is None or amount_sats <= 0:
                return json.dumps(
                    {
                        "success": False,
                        "error": "Invoice has no amount specified. For security, only invoices with explicit amounts are supported.",
                    }
                )
    
            # Check against max_sats
            if amount_sats > max_sats:
                return json.dumps(
                    {
                        "success": False,
                        "error": f"Invoice amount {amount_sats} sats exceeds maximum {max_sats} sats",
                        "amount_sats": amount_sats,
                    }
                )
    
            # Check budget
            if budget_manager and amount_sats:
                try:
                    budget_manager.check_payment(amount_sats, max_sats)
                except Exception as e:
                    return json.dumps(
                        {"success": False, "error": sanitize_error(str(e)), "amount_sats": amount_sats}
                    )
    
            # Pay the invoice
            protocol = "MPP" if is_mpp else "L402"
            logger.info(f"Paying {protocol} invoice for {amount_sats} sats")
            preimage = await wallet.pay_invoice(invoice)
    
            # Record payment
            if budget_manager and amount_sats:
                budget_manager.record_payment(
                    url=f"manual_{protocol.lower()}_payment",
                    amount_sats=amount_sats,
                    invoice=invoice,
                    preimage=preimage,
                    status="success",
                )
    
            # Construct authorization header based on protocol
            if is_mpp:
                authorization_header = f'Payment method="lightning", preimage="{preimage}"'
            else:
                l402_token = f"{macaroon}:{preimage}"
                authorization_header = f"L402 {l402_token}"
    
            result = {
                "success": True,
                "preimage": preimage,
                "amount_sats": amount_sats,
                "protocol": protocol,
                "usage": {
                    "headerName": "Authorization",
                    "headerValue": authorization_header,
                    "protocol": protocol,
                    "description": "Include this header in subsequent requests to the same endpoint",
                },
                "message": (
                    f"Payment successful ({protocol}). Use the authorization header value "
                    f"to access the protected resource."
                ),
            }
    
            # Include token and authorization_header for backward compatibility across protocols
            if is_mpp:
                # For MPP, the token is just the preimage
                result["token"] = preimage
            else:
                # For L402, preserve existing macaroon:preimage token format
                result["token"] = f"{macaroon}:{preimage}"
    
            # Always include the full authorization header
            result["authorization_header"] = authorization_header
    
            return json.dumps(result, indent=2)
    
        except Exception as e:
            logger.exception("Error paying L402/MPP challenge")
            return json.dumps({"success": False, "error": sanitize_error(str(e))})

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/refined-element/lightning-enable-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server