Skip to main content
Glama

ldk_decode_invoice

Decode and validate BOLT11 Lightning invoices to extract payment details and verify transaction information.

Instructions

Decode and validate Lightning invoices

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
invoiceYesBOLT11 Lightning invoice to decode

Implementation Reference

  • src/index.ts:25-62 (registration)
    Import of decodeInvoiceTool and its inclusion in the tools array for MCP server registration
    import { decodeInvoiceTool } from './tools/decodeInvoice.js'; import { listPaymentsTool } from './tools/listPayments.js'; import { estimateFeeTool } from './tools/estimateFee.js'; import { generateMnemonicTool } from './tools/generateMnemonic.js'; import { deriveAddressTool } from './tools/deriveAddress.js'; import { getSwiftCodeTool } from './tools/getSwiftCode.js'; import { getArchitectureTool } from './tools/getArchitecture.js'; import { testScenarioTool } from './tools/testScenario.js'; import { networkGraphTool } from './tools/networkGraph.js'; import { eventHandlingTool } from './tools/eventHandling.js'; import { chainSyncTool } from './tools/chainSync.js'; // Aggregate all tools const tools = [ generateInvoiceTool, payInvoiceTool, getChannelStatusTool, getNodeInfoTool, backupStateTool, keychainTestTool, backgroundTestTool, pushNotificationTool, biometricAuthTool, createChannelTool, closeChannelTool, getBalanceTool, decodeInvoiceTool, listPaymentsTool, estimateFeeTool, generateMnemonicTool, deriveAddressTool, getSwiftCodeTool, getArchitectureTool, testScenarioTool, networkGraphTool, eventHandlingTool, chainSyncTool, ];
  • The execute function of the ldk_decode_invoice tool, which calls LightningService.decodeInvoice and formats the response including Swift example code
    execute: async (args: any): Promise<ToolResult> => { try { const decoded = await lightningService.decodeInvoice(args.invoice); return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...decoded, amountSats: decoded.amountMsat ? Math.floor(decoded.amountMsat / 1000) : null, isExpired: decoded.timestamp + (decoded.expiry || 3600) < Date.now() / 1000, swiftExample: ` // Swift code to decode and display invoice in your iOS app import SwiftUI import LightningDevKit struct InvoiceDecoder: View { @State private var invoiceText = "" @State private var decodedInvoice: DecodedInvoice? @State private var error: String? @State private var showingScanner = false var body: some View { VStack(spacing: 20) { // Input section VStack(alignment: .leading, spacing: 12) { Text("Lightning Invoice") .font(.headline) HStack { TextField("Paste invoice or scan QR", text: $invoiceText) .textFieldStyle(RoundedBorderTextFieldStyle()) .textInputAutocapitalization(.never) .autocorrectionDisabled() .onChange(of: invoiceText) { _ in decodeIfValid() } Button(action: { showingScanner = true }) { Image(systemName: "qrcode.viewfinder") .font(.title2) } Button(action: pasteFromClipboard) { Image(systemName: "doc.on.clipboard") .font(.title2) } } } // Decoded invoice details if let invoice = decodedInvoice { InvoiceDetailsCard(invoice: invoice) } Spacer() } .padding() .navigationTitle("Decode Invoice") .sheet(isPresented: $showingScanner) { QRScannerView { scannedCode in invoiceText = scannedCode showingScanner = false } } .alert("Error", isPresented: .constant(error != nil)) { Button("OK") { error = nil } } message: { Text(error ?? "") } } func decodeIfValid() { guard !invoiceText.isEmpty else { decodedInvoice = nil return } // Decode the invoice let result = Bolt11Invoice.fromStr(s: invoiceText.trimmingCharacters(in: .whitespacesAndNewlines)) if let invoice = result.getValue() { decodedInvoice = DecodedInvoice(from: invoice) error = nil } else { decodedInvoice = nil error = "Invalid Lightning invoice" } } func pasteFromClipboard() { if let text = UIPasteboard.general.string { invoiceText = text } } } struct InvoiceDetailsCard: View { let invoice: DecodedInvoice var body: some View { VStack(alignment: .leading, spacing: 16) { // Status indicator HStack { if invoice.isExpired { Label("Expired", systemImage: "exclamationmark.triangle.fill") .foregroundColor(.red) } else { Label("Valid", systemImage: "checkmark.circle.fill") .foregroundColor(.green) } Spacer() Text(invoice.network.uppercased()) .font(.caption) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.secondary.opacity(0.2)) .cornerRadius(4) } Divider() // Amount if let amountSats = invoice.amountSats { DetailRow( label: "Amount", value: "\\(amountSats.formatted()) sats", icon: "bitcoinsign.circle" ) } else { DetailRow( label: "Amount", value: "Any amount", icon: "bitcoinsign.circle" ) } // Description if let description = invoice.description { DetailRow( label: "Description", value: description, icon: "text.alignleft" ) } // Payment hash DetailRow( label: "Payment Hash", value: invoice.paymentHash.prefix(16) + "...", icon: "number", isMonospaced: true ) // Expiry DetailRow( label: "Expires", value: formatExpiry(invoice.timestamp, invoice.expiry), icon: "clock" ) // Payee if let payee = invoice.payee { DetailRow( label: "Payee", value: payee.prefix(16) + "...", icon: "person.circle", isMonospaced: true ) } } .padding() .background(Color(UIColor.secondarySystemBackground)) .cornerRadius(12) } func formatExpiry(_ timestamp: Int64, _ expiry: Int?) -> String { let expiryTime = Date(timeIntervalSince1970: Double(timestamp + Int64(expiry ?? 3600))) let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .full return formatter.localizedString(for: expiryTime, relativeTo: Date()) } } struct DetailRow: View { let label: String let value: String let icon: String var isMonospaced = false var body: some View { HStack(alignment: .top) { Label(label, systemImage: icon) .foregroundColor(.secondary) .frame(width: 120, alignment: .leading) Text(value) .font(isMonospaced ? .system(.body, design: .monospaced) : .body) .foregroundColor(.primary) .multilineTextAlignment(.trailing) Spacer() } } } struct DecodedInvoice { let paymentHash: String let amountSats: Int? let description: String? let expiry: Int let timestamp: Int64 let payee: String? let network: String let isExpired: Bool init(from invoice: Bolt11Invoice) { self.paymentHash = invoice.paymentHash()?.toHex() ?? "" self.amountSats = invoice.amountMilliSatoshis()?.getValue().map { Int($0 / 1000) } self.description = invoice.description() self.expiry = Int(invoice.expiry()) self.timestamp = Int64(invoice.timestamp()) self.payee = invoice.payee()?.toHex() self.network = invoice.network() == .Bitcoin ? "mainnet" : invoice.network() == .Testnet ? "testnet" : "regtest" self.isExpired = Date().timeIntervalSince1970 > Double(timestamp + Int64(expiry)) } }`.trim() }, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }, null, 2) }], isError: true }; } }
  • Input schema defining the 'invoice' parameter for ldk_decode_invoice tool
    inputSchema: { type: 'object', properties: { invoice: { type: 'string', description: 'BOLT11 Lightning invoice to decode' } }, required: ['invoice'] },
  • Core helper function in LightningService that decodes BOLT11 invoice using bolt11 library and extracts relevant fields
    async decodeInvoice(bolt11Invoice: string): Promise<any> { try { const decoded = bolt11.decode(bolt11Invoice); return { paymentHash: decoded.tags.find(t => t.tagName === 'payment_hash')?.data, amountMsat: decoded.millisatoshis ? parseInt(decoded.millisatoshis) : null, description: decoded.tags.find(t => t.tagName === 'description')?.data, expiry: decoded.tags.find(t => t.tagName === 'expire_time')?.data, timestamp: decoded.timestamp, payee: decoded.payeeNodeKey, network: decoded.network }; } catch (error) { throw new Error(`Failed to decode invoice: ${error}`); } }

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/StevenGeller/ldk-mcp'

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