Skip to main content
Glama

pay_invoice

Pay BOLT11 Lightning invoices directly and receive proof of payment, enabling autonomous transactions for API access, resources, and payments without L402 protocol overhead.

Instructions

Pay a Lightning invoice directly and get the preimage as proof of payment. Use this to pay any BOLT11 Lightning invoice without L402 protocol overhead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
invoiceYesBOLT11 Lightning invoice string to pay
max_satsNoMaximum satoshis allowed to pay. Defaults to 1000
confirmedNoSet to true to confirm a payment that requires approval. Use when previous call returned requiresConfirmation=true.

Implementation Reference

  • The pay_invoice tool implementation, which includes budget validation, confirmation checks, and execution of the wallet's pay_invoice method.
    async def pay_invoice(
        invoice: str,
        max_sats: int = 1000,
        confirmed: bool = False,
        wallet: "Union[NWCWallet, OpenNodeWallet, None]" = None,
        budget_manager: "BudgetManager | None" = None,
        budget_service: "BudgetService | None" = None,
    ) -> str:
        """
        Pay a Lightning invoice directly and get the preimage as proof of payment.
    
        This tool allows direct payment of any BOLT11 Lightning invoice without
        the L402 protocol overhead. Useful for tipping, donations, or paying
        for services that accept Lightning directly.
    
        NOTE: Most MCP clients (including Claude Code) don't support elicitation yet.
        Payments above auto_approve threshold require explicit confirmation
        by calling this tool again with confirmed=True.
    
        Args:
            invoice: BOLT11 Lightning invoice string to pay
            max_sats: Maximum satoshis allowed to pay. Defaults to 1000
            confirmed: Set to True to confirm a payment above the auto-approve threshold
            wallet: Wallet instance (NWC or OpenNode)
            budget_manager: Legacy budget manager (deprecated, use budget_service)
            budget_service: BudgetService for multi-tier approval logic
    
        Returns:
            JSON with payment result including preimage or error message
        """
        # Validate invoice is provided
        if not invoice or not invoice.strip():
            return json.dumps({
                "success": False,
                "error": "Invoice is required"
            })
    
        if not wallet:
            return json.dumps({
                "success": False,
                "error": "Wallet not configured. Set NWC_CONNECTION_STRING or OPENNODE_API_KEY environment variable."
            })
    
        try:
            # Normalize invoice to lowercase
            normalized_invoice = invoice.strip().lower()
    
            # Basic validation - must be a BOLT11 invoice
            if not normalized_invoice.startswith("lnbc") and not normalized_invoice.startswith("lntb"):
                return json.dumps({
                    "success": False,
                    "error": "Invalid invoice format. Must be a BOLT11 invoice starting with 'lnbc' (mainnet) or 'lntb' (testnet)"
                })
    
            # Use new BudgetService if available, otherwise fall back to legacy BudgetManager
            if budget_service:
                # Check approval level using new multi-tier system
                result = await budget_service.check_approval_level(max_sats)
    
                if result.level == ApprovalLevel.DENY:
                    return json.dumps({
                        "success": False,
                        "error": "Payment denied by budget policy",
                        "denialReason": result.denial_reason,
                        "budget": {
                            "requestedSats": max_sats,
                            "requestedUsd": float(result.amount_usd),
                            "remainingSessionUsd": float(result.remaining_session_budget_usd),
                        },
                        "note": "Edit ~/.lightning-enable/config.json to change limits."
                    })
    
                # Check if payment requires confirmation (FORM_CONFIRM or URL_CONFIRM)
                if result.requires_confirmation and not confirmed:
                    return json.dumps({
                        "success": False,
                        "requiresConfirmation": True,
                        "approvalLevel": result.level.value,
                        "error": "Payment requires your confirmation",
                        "message": result.confirmation_message or f"Approve payment of ${result.amount_usd:.2f} ({max_sats:,} sats)?",
                        "howToConfirm": 'Call: pay_invoice(invoice="...", confirmed=True)',
                        "amount": {
                            "sats": max_sats,
                            "usd": float(result.amount_usd)
                        },
                        "budget": {
                            "remainingSessionUsd": float(result.remaining_session_budget_usd),
                        }
                    })
    
                # LOG_AND_APPROVE: Log for user awareness but proceed
                if result.level == ApprovalLevel.LOG_AND_APPROVE:
                    logger.info(f"Log-and-approve payment: {max_sats} sats (${result.amount_usd:.2f})")
    
            elif budget_manager:
                # Legacy budget manager fallback
                try:
                    budget_manager.check_payment(max_sats)
                except Exception as e:
                    return json.dumps({
                        "success": False,
                        "error": sanitize_error(str(e)),
                        "budget": {
                            "requested_sats": max_sats,
                            "remaining_sats": budget_manager.max_per_session - budget_manager.session_spent
                        }
                    })
    
                # Check if payment requires confirmation (above auto_approve threshold)
                auto_approve_sats = getattr(budget_manager, 'auto_approve_sats', 1000)
                if max_sats > auto_approve_sats and not confirmed:
                    # Estimate USD value (~$0.001 per sat at ~$100k/BTC)
                    estimated_usd = max_sats * 0.001
                    return json.dumps({
                        "success": False,
                        "requiresConfirmation": True,
                        "error": "Payment requires your confirmation",
                        "message": f"This payment of ~${estimated_usd:.2f} ({max_sats:,} sats) exceeds the auto-approve threshold of {auto_approve_sats:,} sats. "
                                  "To proceed, call pay_invoice again with confirmed=True.",
                        "howToConfirm": 'Call: pay_invoice(invoice="...", confirmed=True)',
                        "amount": {
                            "sats": max_sats,
                            "estimatedUsd": round(estimated_usd, 2)
                        },
                        "thresholds": {
                            "autoApprove": auto_approve_sats,
                            "note": "Payments above this require confirmation"
                        }
                    })
    
            # Pay the invoice
            logger.info(f"Paying invoice: {normalized_invoice[:30]}...")
            preimage = await wallet.pay_invoice(normalized_invoice)
    
            if not preimage:
                # Record failed payment
                if budget_manager:
                    budget_manager.record_payment(
                        url="direct-invoice",
                        amount_sats=max_sats,
                        invoice=normalized_invoice,
                        preimage="",
                        status="failed",
                    )
                return json.dumps({
                    "success": False,
                    "error": "Payment failed - no preimage returned"
                })
    
            # Record the payment
            if budget_service:
                budget_service.record_spend(max_sats)
                budget_service.record_payment_time()
    
                # Get updated session info
                status = budget_service.get_status()
                session_info = {
                    "spentSats": status["session"]["spentSats"],
                    "spentUsd": status["session"]["spentUsd"],
                    "remainingUsd": status["session"]["remainingUsd"],
                    "requestCount": status["session"]["requestCount"],
                }
            elif budget_manager:
                budget_manager.record_payment(
                    url="direct-invoice",
                    amount_sats=max_sats,
                    invoice=normalized_invoice,
                    preimage=preimage,
                    status="success",
                )
                session_info = {
                    "spentSats": budget_manager.session_spent,
                    "remainingSats": budget_manager.max_per_session - budget_manager.session_spent,
                }
            else:
                session_info = None
    
            # Return success with preimage
            response = {
                "success": True,
                "preimage": preimage,
                "message": "Payment successful",
                "invoice": {
                    "paid": normalized_invoice[:30] + "..." if len(normalized_invoice) > 30 else normalized_invoice
                }
            }
    
            if session_info:
                response["session"] = session_info
    
            return json.dumps(response, indent=2)
    
        except Exception as e:
            logger.exception("Error paying invoice")
    
            # Record failed payment
            if budget_manager:
                budget_manager.record_payment(
                    url="direct-invoice",
                    amount_sats=0,
                    invoice=invoice,
                    preimage="",
                    status="failed",
                )
    
            return json.dumps({
                "success": False,
                "error": sanitize_error(str(e))
            })
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions the output ('preimage as proof of payment') but lacks details on permissions, rate limits, error handling, or what happens if payment fails. For a payment tool with zero annotation coverage, this is insufficient for safe agent use.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with zero waste: the first states the purpose and output, and the second provides usage context. It is front-loaded with key information and appropriately sized for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description should do more to explain behavioral aspects like payment confirmation flow (hinted by the 'confirmed' parameter) or error cases. It covers basic purpose and usage but lacks completeness for a payment operation with potential financial implications.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents the parameters. The description does not add any semantic details beyond what the schema provides (e.g., it doesn't explain invoice format or max_sats implications). Baseline 3 is appropriate as the schema handles parameter documentation adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Pay a Lightning invoice directly') and the resource ('BOLT11 Lightning invoice'), distinguishing it from siblings like 'pay_l402_challenge' by specifying 'without L402 protocol overhead'. It also mentions the output ('get the preimage as proof of payment'), making the purpose specific and well-defined.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool ('to pay any BOLT11 Lightning invoice without L402 protocol overhead'), implicitly distinguishing it from 'pay_l402_challenge'. However, it does not explicitly state when not to use it or mention alternatives like 'send_onchain' for non-Lightning payments, leaving some guidance gaps.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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