Main Menu
...
Monitoring
GRPC
6 min
monitoring with lnd's grpc interface lnd's grpc interface provides the most efficient and feature complete way to interact with your lightning node it offers bidirectional streaming, strongly typed responses, and access to all lnd functionality grpc is ideal for server side applications that need low latency, persistent connections with your node setup first, you'll need to download the lnd proto files and install the necessary dependencies js setup (bash) # download the proto files for your lnd version curl o lightning proto https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/lightning proto \# install dependencies (javascript) npm install @grpc/grpc js @grpc/proto loader python setup (bash) # 1 grab the proto files that match your lnd version \# (π this pulls the whole repoβs protos so you have lightning, router, invoices, etc ) git clone depth 1 https //github com/lightningnetwork/lnd git cd lnd/lnrpc \# 2 install the python grpc toolchain and http / websocket helpers python m pip install upgrade \\ grpcio grpcio tools protobuf \\ requests websocket client\<?php // ensure the grpc extension is loaded and proto classes are autoloaded require 'vendor/autoload php'; use grpc\channelcredentials; use lnrpc\lightningclient; use invoicesrpc\invoicesclient; use routerrpc\routerclient; // environment and file paths putenv('grpc ssl cipher suites=high+ecdsa'); // required by lnd for grpc tls $grpchost = getenv('grpc host') ? 'localhost 10009'; $tlscertpath = getenv('lnd tls cert') ? '/path/to/tls cert'; $macaroonpath = getenv('lnd macaroon path') ? '/path/to/admin macaroon'; // read tls certificate and macaroon $tlscert = file get contents($tlscertpath); $macaroon = file get contents($macaroonpath); $metadatacallback = function($metadata) use ($macaroon) { // add macaroon as hex in metadata for each call return \['macaroon' => \[bin2hex($macaroon)]]; }; $credentials = channelcredentials createssl($tlscert); $opts = \[ 'credentials' => $credentials, 'update metadata' => $metadatacallback ]; subscribe to single invoice monitor updates for a specific invoice using its payment hash lnd method https //lightning engineering/api docs/api/lnd/invoices/subscribe single invoice/ https //lightning engineering/api docs/api/lnd/invoices/subscribe single invoice/ // grpc single invoice js monitor a single invoice via grpc streaming const fs = require("fs"); const grpc = require("@grpc/grpc js"); const protoloader = require("@grpc/proto loader"); // configuration const lnd host = process env lnd host; // e g , "your node voltageapp io" const macaroon path = process env macaroon path; // path to admin macaroon const proto path = process env proto path || " /lightning proto"; const r hash = process env r hash; // base64 encoded r hash of the invoice if (!lnd host || !macaroon path || !r hash) { console error("β please set lnd host, macaroon path, and r hash env vars "); process exit(1); } // grpc loader options const loaderoptions = { keepcase true, longs string, enums string, defaults true, oneofs true, }; // load proto definition const packagedefinition = protoloader loadsync(proto path, loaderoptions); async function subscribetosingleinvoice() { // set up ssl cipher suites process env grpc ssl cipher suites = "high+ecdsa"; // read and prepare macaroon const macaroonbytes = fs readfilesync(macaroon path); const macaroonhex = macaroonbytes tostring("hex"); // create metadata with macaroon const metadata = new grpc metadata(); metadata add("macaroon", macaroonhex); // create credentials const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); const sslcreds = grpc credentials createssl(); const credentials = grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); // create grpc client const lnrpcdescriptor = grpc loadpackagedefinition(packagedefinition); const lnrpc = lnrpcdescriptor lnrpc; const client = new lnrpc invoices( `${lnd host} 10009`, credentials ); console log(`π‘ subscribing to invoice ${r hash} `); // subscribe to single invoice const call = client subscribesingleinvoice({ r hash buffer from(r hash, "base64"), }); call on("data", (invoice) => { console log("\nπ¦ invoice update received "); console log(` β’ state ${invoice state}`); console log(` β’ amount paid ${invoice amt paid sat} sats`); console log(` β’ settled ${invoice settled}`); console log(` β’ settle date ${invoice settle date}`); if (invoice settled) { console log("β
invoice has been settled!"); call cancel(); } }); call on("error", (err) => { if (err code !== grpc status cancelled) { console error("β stream error ", err message); } }); call on("end", () => { console log("π stream ended "); }); } // run the subscription subscribetosingleinvoice() catch(console error);import os, codecs, grpc \# generated grpc classes from protos import invoices pb2 as invoicesrpc import invoices pb2 grpc as invoicesstub grpc host = os getenv("lnd grpc host", "localhost 10009") tls cert path = os getenv("lnd tls cert", "tls cert") macaroon path = os getenv("lnd macaroon path") # path to admin macaroon file invoice hash hex = os getenv("lnd invoice hash") if not (macaroon path and invoice hash hex) raise environmenterror("please set lnd macaroon path and lnd invoice hash env variables ") \# setup tls and macaroon credentials for grpc os environ\["grpc ssl cipher suites"] = "high+ecdsa" cert = open(tls cert path, "rb") read() ssl creds = grpc ssl channel credentials(cert) macaroon bytes = open(macaroon path, "rb") read() macaroon hex = codecs encode(macaroon bytes, "hex") # hex string def metadata callback(context, callback) callback(\[("macaroon", macaroon hex)], none) auth creds = grpc metadata call credentials(metadata callback) combined creds = grpc composite channel credentials(ssl creds, auth creds) channel = grpc secure channel(grpc host, combined creds) \# create a stub for the invoices service invoices stub = invoicesstub invoicesstub(channel) \# subscribe to a single invoice by its r hash invoice hash bytes = bytes fromhex(invoice hash hex) request = invoicesrpc subscribesingleinvoicerequest(r hash=invoice hash bytes) print(f"subscribing to invoice (hash={invoice hash hex}) via grpc ") updates = invoices stub subscribesingleinvoice(request) for invoice update in updates print(invoice update) # invoice update is an invoice message print("invoice subscription stream ended ")// subscribe to a single invoice's state changes via grpc $invoicehashhex = getenv('invoice hash hex') ? '\<invoice hash in hex>'; $invoiceclient = new invoicesclient($grpchost, $opts); // prepare request with the invoice rhash (as raw bytes) $invoicereq = new invoicesrpc\subscribesingleinvoicerequest(); $invoicereq >setrhash(hex2bin($invoicehashhex)); // initiate the rpc stream $invoicestream = $invoiceclient >subscribesingleinvoice($invoicereq); echo "subscribed to invoice {$invoicehashhex} (single invoice stream) \n"; // read streamed invoice updates while (true) { / @var lnrpc\invoice $invoiceupdate / $invoiceupdate = $invoicestream >read(); if ($invoiceupdate === null) { // stream ended (invoice reached a final state and stream closed) echo "invoice stream closed by server \n"; break; } // log the invoice's current state and amount paid $state = $invoiceupdate >getstate(); // e g , 0=open, 1=settled, 2=canceled $settledflag = $invoiceupdate >getsettled(); // boolean echo "π invoice update state=" ($settledflag ? 'settled' 'open') ", amount paid sat=" $invoiceupdate >getamtpaidsat() php eol; if ($settledflag || $state === \lnrpc\invoice\invoicestate canceled) { // stop if invoice is settled or canceled echo "invoice is now final (settled or canceled) ending subscription \n"; break; } } subscribe to all invoices monitor all invoice activity on your node, with the ability to resume from a specific point lnd method https //lightning engineering/api docs/api/lnd/lightning/subscribe invoices/ https //lightning engineering/api docs/api/lnd/lightning/subscribe invoices/ // grpc all invoices js subscribe to all invoice updates via grpc const fs = require("fs"); const grpc = require("@grpc/grpc js"); const protoloader = require("@grpc/proto loader"); // configuration const lnd host = process env lnd host; const macaroon path = process env macaroon path; const proto path = process env proto path || " /lightning proto"; // resume points (0 = start from beginning) const add index = process env add index || "0"; const settle index = process env settle index || "0"; if (!lnd host || !macaroon path) { console error("β please set lnd host and macaroon path env vars "); process exit(1); } const loaderoptions = { keepcase true, longs string, enums string, defaults true, oneofs true, }; const packagedefinition = protoloader loadsync(proto path, loaderoptions); async function subscribetoinvoices() { process env grpc ssl cipher suites = "high+ecdsa"; // set up credentials const macaroonbytes = fs readfilesync(macaroon path); const macaroonhex = macaroonbytes tostring("hex"); const metadata = new grpc metadata(); metadata add("macaroon", macaroonhex); const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); const sslcreds = grpc credentials createssl(); const credentials = grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); // create client const lnrpcdescriptor = grpc loadpackagedefinition(packagedefinition); const lnrpc = lnrpcdescriptor lnrpc; const client = new lnrpc lightning(`${lnd host} 10009`, credentials); console log(`π‘ subscribing to invoices (add index ${add index}, settle index ${settle index}) `); // subscribe with resume points const call = client subscribeinvoices({ add index add index, settle index settle index, }); // track invoice counts let invoicecount = 0; let settledcount = 0; call on("data", (invoice) => { invoicecount++; console log(`\nπ¦ invoice #${invoicecount} `); console log(` β’ hash ${buffer from(invoice r hash) tostring("base64")}`); console log(` β’ state ${invoice state}`); console log(` β’ value ${invoice value} sats`); console log(` β’ amount paid ${invoice amt paid sat} sats`); console log(` β’ add index ${invoice add index}`); if (invoice settled) { settledcount++; console log(` β’ settle index ${invoice settle index}`); console log(` β’ β
settled (total settled ${settledcount})`); } }); call on("error", (err) => { console error("β stream error ", err message); }); call on("end", () => { console log("π invoice stream ended "); }); // graceful shutdown process on("sigint", () => { console log("\nβΉοΈ shutting down "); call cancel(); process exit(0); }); } subscribetoinvoices() catch(console error);import os, codecs, grpc \# generated classes from lightning proto (lnrpc) import lightning pb2 as lnrpc import lightning pb2 grpc as lightningstub grpc host = os getenv("lnd grpc host", "localhost 10009") tls cert path = os getenv("lnd tls cert", "tls cert") macaroon path = os getenv("lnd macaroon path") if not macaroon path raise environmenterror("please set lnd macaroon path env variable ") \# set up credentials (tls and macaroon) for grpc os environ\["grpc ssl cipher suites"] = "high+ecdsa" cert = open(tls cert path, "rb") read() ssl creds = grpc ssl channel credentials(cert) macaroon bytes = open(macaroon path, "rb") read() macaroon hex = codecs encode(macaroon bytes, "hex") def metadata callback(context, callback) callback(\[("macaroon", macaroon hex)], none) auth creds = grpc metadata call credentials(metadata callback) combined creds = grpc composite channel credentials(ssl creds, auth creds) channel = grpc secure channel(grpc host, combined creds) lightning stub = lightningstub lightningstub(channel) \# subscribe to all invoice events (no indexes specified for new events only) request = lnrpc invoicesubscription() # leaving add index and settle index unset print("subscribing to all invoices via grpc ") for invoice in lightning stub subscribeinvoices(request) \# each 'invoice' is an invoice message (new or updated) print(invoice)// subscribe to all invoice add/settle events via grpc $lightningclient = new lightningclient($grpchost, $opts); $subreq = new lnrpc\invoicesubscription(); // empty request = subscribe from latest // (optionally, set add index and/or settle index on $subreq to resume from a specific point) $invoicestream = $lightningclient >subscribeinvoices($subreq); echo "subscribed to all invoice events \n"; // stream all invoice updates continuously while (true) { $invoice = $invoicestream >read(); if ($invoice === null) { echo "invoice event stream closed \n"; break; } $hashhex = bin2hex($invoice >getrhash()); $statestr = $invoice >getsettled() ? 'settled' ($invoice >getstate() === \lnrpc\invoice\invoicestate canceled ? 'canceled' 'open'); echo "π£ invoice #{$invoice >getaddindex()} ({$hashhex}) {$statestr}, value={$invoice >getvaluesat()} sat\n"; // (process invoice data as needed; this loop continues indefinitely) } track single payment monitor the progress of a specific outgoing payment lnd method https //lightning engineering/api docs/api/lnd/router/track payment v2/ https //lightning engineering/api docs/api/lnd/router/track payment v2/ // grpc track payment js track a single payment via grpc const fs = require("fs"); const grpc = require("@grpc/grpc js"); const protoloader = require("@grpc/proto loader"); // configuration const lnd host = process env lnd host; const macaroon path = process env macaroon path; const proto path = process env proto path || " /lightning proto"; const payment hash = process env payment hash; // hex or base64 if (!lnd host || !macaroon path || !payment hash) { console error("β please set lnd host, macaroon path, and payment hash env vars "); process exit(1); } const loaderoptions = { keepcase true, longs string, enums string, defaults true, oneofs true, }; const packagedefinition = protoloader loadsync(proto path, loaderoptions); async function trackpayment() { process env grpc ssl cipher suites = "high+ecdsa"; // set up credentials const macaroonbytes = fs readfilesync(macaroon path); const macaroonhex = macaroonbytes tostring("hex"); const metadata = new grpc metadata(); metadata add("macaroon", macaroonhex); const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); const sslcreds = grpc credentials createssl(); const credentials = grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); // create router client const lnrpcdescriptor = grpc loadpackagedefinition(packagedefinition); const routerrpc = lnrpcdescriptor routerrpc; const client = new routerrpc router(`${lnd host} 10009`, credentials); console log(`π‘ tracking payment ${payment hash} `); // convert payment hash to buffer let paymenthashbuffer; if (payment hash length === 64) { // hex format paymenthashbuffer = buffer from(payment hash, "hex"); } else { // base64 format paymenthashbuffer = buffer from(payment hash, "base64"); } // track payment const call = client trackpaymentv2({ payment hash paymenthashbuffer, no inflight updates false, // get all updates, not just final }); let attemptcount = 0; call on("data", (payment) => { attemptcount++; console log(`\nπΈ payment update #${attemptcount} `); console log(` β’ status ${payment status}`); console log(` β’ value ${payment value sat} sats`); console log(` β’ fee ${payment fee sat} sats`); console log(` β’ htlcs ${payment htlcs? length || 0}`); if (payment failure reason) { console log(` β’ failure ${payment failure reason}`); } // log htlc attempts if (payment htlcs && payment htlcs length > 0) { console log(" β’ attempts "); payment htlcs foreach((htlc, idx) => { console log(` ${idx + 1} status ${htlc status}, route ${htlc route? hops? length || 0} hops`); }); } if (payment status === "succeeded") { console log("β
payment successful!"); call cancel(); } else if (payment status === "failed") { console log("β payment failed!"); call cancel(); } }); call on("error", (err) => { if (err code !== grpc status cancelled) { console error("β stream error ", err message); } }); call on("end", () => { console log("π payment tracking ended "); }); } trackpayment() catch(console error);import os, codecs, grpc \# generated classes from router proto (routerrpc) import router pb2 as routerrpc import router pb2 grpc as routerstub grpc host = os getenv("lnd grpc host", "localhost 10009") tls cert path = os getenv("lnd tls cert", "tls cert") macaroon path = os getenv("lnd macaroon path") payment hash hex = os getenv("lnd payment hash") if not (macaroon path and payment hash hex) raise environmenterror("please set lnd macaroon path and lnd payment hash env variables ") \# set up secure channel with credentials os environ\["grpc ssl cipher suites"] = "high+ecdsa" cert = open(tls cert path, "rb") read() ssl creds = grpc ssl channel credentials(cert) macaroon bytes = open(macaroon path, "rb") read() macaroon hex = codecs encode(macaroon bytes, "hex") def metadata callback(context, callback) callback(\[("macaroon", macaroon hex)], none) auth creds = grpc metadata call credentials(metadata callback) combined creds = grpc composite channel credentials(ssl creds, auth creds) channel = grpc secure channel(grpc host, combined creds) router stub = routerstub routerstub(channel) \# track a single outgoing payment by its hash payment hash bytes = bytes fromhex(payment hash hex) request = routerrpc trackpaymentrequest(payment hash=payment hash bytes) print(f"tracking payment {payment hash hex} via grpc ") for payment update in router stub trackpaymentv2(request) print(payment update) print("payment tracking stream ended ")// track a single payment's progress via grpc $paymenthashhex = getenv('payment hash hex') ? '\<payment hash in hex>'; $routerclient = new routerclient($grpchost, $opts); $trackreq = new routerrpc\trackpaymentrequest(); $trackreq >setpaymenthash(hex2bin($paymenthashhex)); $trackreq >setnoinflightupdates(false); // set true to only get final update $paymentstream = $routerclient >trackpaymentv2($trackreq); echo "tracking payment {$paymenthashhex} (grpc) \n"; // read payment status updates while (true) { / @var lnrpc\payment $payment / $payment = $paymentstream >read(); if ($payment === null) { echo "payment stream closed by server \n"; break; } $status = $payment >getstatus(); // an enum of type paymentstatus // map status to human readable form $statusname = \['unknown', 'in flight', 'succeeded', 'failed']\[$status] ?? $status; echo "π± payment update status={$statusname}"; if ($statusname === 'succeeded') { $preimage = bin2hex($payment >getpaymentpreimage()); echo ", preimage={$preimage}\n"; echo "β
payment succeeded, amount sent={$payment >getvaluesat()} sat, total fee={$payment >getfeesat()} sat\n"; break; } elseif ($statusname === 'failed') { echo "\nβ payment failed (failure reason {$payment >getfailurereason()}) \n"; break; } else { echo " (still in flight)\n"; // continue loop for more intermediate updates (e g htlc attempts) } } track all payments monitor all outgoing payment activity on your node in real time lnd method https //lightning engineering/api docs/api/lnd/router/track payments/ https //lightning engineering/api docs/api/lnd/router/track payments/ // grpc track all payments js track all payments via grpc streaming const fs = require("fs"); const grpc = require("@grpc/grpc js"); const protoloader = require("@grpc/proto loader"); // configuration const lnd host = process env lnd host; const macaroon path = process env macaroon path; const proto path = process env proto path || " /lightning proto"; const no inflight = process env no inflight === "true"; // show only final states if (!lnd host || !macaroon path) { console error("β please set lnd host and macaroon path env vars "); process exit(1); } const loaderoptions = { keepcase true, longs string, enums string, defaults true, oneofs true, }; const packagedefinition = protoloader loadsync(proto path, loaderoptions); async function trackallpayments() { process env grpc ssl cipher suites = "high+ecdsa"; // set up credentials const macaroonbytes = fs readfilesync(macaroon path); const macaroonhex = macaroonbytes tostring("hex"); const metadata = new grpc metadata(); metadata add("macaroon", macaroonhex); const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); const sslcreds = grpc credentials createssl(); const credentials = grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); // create router client const lnrpcdescriptor = grpc loadpackagedefinition(packagedefinition); const routerrpc = lnrpcdescriptor routerrpc; const client = new routerrpc router(`${lnd host} 10009`, credentials); console log(`π‘ tracking all payments (no inflight updates ${no inflight}) `); // track all payments const call = client trackpayments({ no inflight updates no inflight, }); // track statistics const stats = { total 0, inflight 0, succeeded 0, failed 0, totalfees 0, totalsent 0, }; call on("data", (payment) => { stats total++; console log(`\nπΈ payment event #${stats total} `); console log(` β’ hash ${buffer from(payment payment hash) tostring("hex") substring(0, 16)} `); console log(` β’ status ${payment status}`); console log(` β’ value ${payment value sat} sats`); console log(` β’ fee ${payment fee sat} sats`); console log(` β’ created ${new date(parseint(payment creation time ns) / 1000000) toisostring()}`); // update statistics switch (payment status) { case "in flight" stats inflight++; break; case "succeeded" stats succeeded++; stats totalfees += parseint(payment fee sat || 0); stats totalsent += parseint(payment value sat || 0); console log(` β’ β
success! total succeeded ${stats succeeded}`); break; case "failed" stats failed++; console log(` β’ β failed! reason ${payment failure reason}`); break; } // show running totals console log(`\nπ statistics `); console log(` β’ in flight ${stats inflight}`); console log(` β’ succeeded ${stats succeeded}`); console log(` β’ failed ${stats failed}`); console log(` β’ total sent ${stats totalsent} sats`); console log(` β’ total fees ${stats totalfees} sats`); }); call on("error", (err) => { console error("β stream error ", err message); }); call on("end", () => { console log("π payment stream ended "); }); // graceful shutdown process on("sigint", () => { console log("\nβΉοΈ shutting down "); console log("final statistics ", stats); call cancel(); process exit(0); }); } trackallpayments() catch(console error);import os, codecs, grpc import router pb2 as routerrpc import router pb2 grpc as routerstub grpc host = os getenv("lnd grpc host", "localhost 10009") tls cert path = os getenv("lnd tls cert", "tls cert") macaroon path = os getenv("lnd macaroon path") if not macaroon path raise environmenterror("please set lnd macaroon path env variable ") \# setup grpc channel credentials (tls + macaroon) os environ\["grpc ssl cipher suites"] = "high+ecdsa" cert = open(tls cert path, "rb") read() ssl creds = grpc ssl channel credentials(cert) macaroon bytes = open(macaroon path, "rb") read() macaroon hex = codecs encode(macaroon bytes, "hex") def metadata callback(context, callback) callback(\[("macaroon", macaroon hex)], none) auth creds = grpc metadata call credentials(metadata callback) combined creds = grpc composite channel credentials(ssl creds, auth creds) channel = grpc secure channel(grpc host, combined creds) router stub = routerstub routerstub(channel) \# subscribe to updates for all payments (ongoing and new) request = routerrpc trackpaymentsrequest(no inflight updates=false) print("subscribing to all outgoing payments via grpc ") for payment in router stub trackpayments(request) print(payment)// subscribe to updates for all in flight payments via grpc $routerclient = new routerclient($grpchost, $opts); $streamreq = new routerrpc\trackpaymentsrequest(); $streamreq >setnoinflightupdates(false); // set true to only get final outcomes $paymentsstream = $routerclient >trackpayments($streamreq); echo "subscribed to all payments stream \n"; while (true) { / @var lnrpc\payment $payment / $payment = $paymentsstream >read(); if ($payment === null) { echo "payments stream closed (no more data) \n"; break; } $hashhex = $payment >getpaymenthash(); $status = $payment >getstatus(); $statusname = \['unknown','in flight','succeeded','failed']\[$status] ?? $status; echo "π€ payment {$payment >getpaymentindex()} (hash={$hashhex}) {$statusname}"; if ($statusname === 'succeeded') { echo " (sent {$payment >getvaluesat()} sat, fee {$payment >getfeesat()} sat)"; } elseif ($statusname === 'failed') { echo " (failed reason={$payment >getfailurereason()})"; } echo php eol; // (the loop continues to stream updates for all ongoing/new payments) }