Menu
Lightning JavaScript Executable Jan 31, 2026

Parsing BOLT11 Lightning Invoices

Decode and validate BOLT11 invoice strings in JavaScript

#bolt11 #invoice #parsing #bech32

Overview

BOLT11 is the standard invoice format for Lightning Network payments. This code demonstrates how to parse and validate BOLT11 invoices for use in agent payment flows.

The Code

/**
 * BOLT11 Invoice Parser
 * Decodes Lightning invoices for agents
 *
 * Note: This is a simplified implementation.
 * For production use, use a library like 'bolt11' or 'light-bolt11-decoder'
 */

const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';

/**
 * Bech32 decode (simplified)
 */
function bech32Decode(str) {
  const lower = str.toLowerCase();
  const sepIndex = lower.lastIndexOf('1');

  if (sepIndex < 1) throw new Error('Invalid bech32: no separator');

  const hrp = lower.slice(0, sepIndex);
  const data = lower.slice(sepIndex + 1);

  const values = [];
  for (const char of data) {
    const val = ALPHABET.indexOf(char);
    if (val === -1) throw new Error('Invalid bech32 character');
    values.push(val);
  }

  return { hrp, data: values.slice(0, -6) }; // Remove checksum
}

/**
 * Parse BOLT11 invoice
 * @param {string} invoice - BOLT11 invoice string
 * @returns {Object} Decoded invoice data
 */
function parseBolt11(invoice) {
  // Validate prefix
  if (!invoice.toLowerCase().startsWith('ln')) {
    throw new Error('Invalid BOLT11: must start with "ln"');
  }

  const { hrp, data } = bech32Decode(invoice);

  // Parse HRP: ln + network + amount (optional)
  const networkMatch = hrp.match(/^ln(bc|tb|bcrt)(\d+[munp]?)?$/);
  if (!networkMatch) {
    throw new Error('Invalid BOLT11 network prefix');
  }

  const networks = {
    'bc': 'mainnet',
    'tb': 'testnet',
    'bcrt': 'regtest'
  };

  const multipliers = {
    'm': 0.001,
    'u': 0.000001,
    'n': 0.000000001,
    'p': 0.000000000001
  };

  const network = networks[networkMatch[1]];
  let amountBtc = null;
  let amountMsat = null;

  if (networkMatch[2]) {
    const amountStr = networkMatch[2];
    const multiplier = amountStr.slice(-1);
    const value = parseInt(amountStr.slice(0, -1) || amountStr);

    if (multipliers[multiplier]) {
      amountBtc = value * multipliers[multiplier];
    } else {
      amountBtc = value;
    }
    amountMsat = Math.round(amountBtc * 100000000000);
  }

  // Extract timestamp (first 35 bits = 7 groups of 5 bits)
  let timestamp = 0;
  for (let i = 0; i < 7; i++) {
    timestamp = timestamp * 32 + data[i];
  }

  return {
    network,
    amountBtc,
    amountMsat,
    timestamp,
    timestampDate: new Date(timestamp * 1000).toISOString(),
    raw: {
      hrp,
      dataLength: data.length
    }
  };
}

// Example usage
const testInvoice = 'lnbc10u1pjq2ywdpp5...'; // Truncated for example

try {
  const decoded = parseBolt11(testInvoice);
  console.log('Decoded invoice:', JSON.stringify(decoded, null, 2));
} catch (err) {
  console.error('Parse error:', err.message);
}

module.exports = { parseBolt11, bech32Decode };

Usage

# Install as a module (if using in a project)
npm install light-bolt11-decoder  # Recommended for production

# Or use the code above directly
node bolt11-parser.js

Example Output

{
  "network": "mainnet",
  "amountBtc": 0.00001,
  "amountMsat": 1000000,
  "timestamp": 1706659200,
  "timestampDate": "2024-01-31T00:00:00.000Z",
  "raw": {
    "hrp": "lnbc10u",
    "dataLength": 293
  }
}

Agent Notes

Before paying an invoice:

  1. Check expiry: Invoices have a default 1-hour expiry. Check timestamp + expiry against current time.

  2. Verify amount: If amountMsat is null, the invoice is “zero-amount” and the payer chooses the amount.

  3. Network match: Ensure your wallet network matches the invoice network (mainnet vs testnet).

  4. Single use: BOLT11 invoices should only be paid once. Paying twice may fail or lose funds.

For production agents, use established libraries:

  • JavaScript: bolt11, light-bolt11-decoder
  • Python: bolt11
  • Rust: lightning-invoice