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
| Name | Required | Description | Default |
|---|---|---|---|
| invoice | Yes | BOLT11 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 registrationimport { 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, ];
- src/tools/decodeInvoice.ts:19-265 (handler)The execute function of the ldk_decode_invoice tool, which calls LightningService.decodeInvoice and formats the response including Swift example codeexecute: 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 }; } }
- src/tools/decodeInvoice.ts:9-18 (schema)Input schema defining the 'invoice' parameter for ldk_decode_invoice toolinputSchema: { 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 fieldsasync 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}`); } }