Main Menu
Implementation Cheatsheet
Configuration
16 min
basic setup lnd provides multiple interfaces for interacting with your lightning node, each suited to different application architectures and requirements this guide covers configuring rest, websocket, and grpc clients to connect with your lnd node using proper authentication and connection management each interface offers different advantages rest for simple http integrations, websockets for real time browser applications, and grpc for high performance server side implementations all methods use the same macaroon based authentication system but require different client setup approaches rest configuration rest provides a familiar http based interface that works with standard web libraries and tools it's the simplest to implement and debug, making it ideal for web applications, simple integrations, and development environments 1\ configure api client create an authenticated http client that will handle all communication with your lnd node's rest interface the client configuration includes the node's rest endpoint url and the required authentication headers const lnd = axios create({ baseurl https //${host} 8080, headers { "content type" "application/json", "grpc metadata macaroon" macaroon } });import requests from typing import dict, any import json \# 1 configure api client class lndclient def init (self, host str, macaroon str) self base url = f"https //{host} 8080" self headers = { "content type" "application/json", "grpc metadata macaroon" macaroon } self session = requests session() self session headers update(self headers) \# if you need to disable ssl verification for development (not recommended for production) \# self session verify = false def get(self, endpoint str) > dict\[str, any] response = self session get(f"{self base url}{endpoint}") response raise for status() return response json() def post(self, endpoint str, data dict\[str, any]) > dict\[str, any] response = self session post(f"{self base url}{endpoint}", json=data) response raise for status() return response json() \# initialize the client lnd = lndclient(host="your node host", macaroon="your macaroon hex")// configure api client using curl class lndclient { private $baseurl; private $macaroon; public function construct($host, $macaroon) { $this >baseurl = "https //{$host} 8080"; $this >macaroon = $macaroon; } private function executerequest($method, $endpoint, $data = null) { $ch = curl init(); $url = $this >baseurl $endpoint; curl setopt($ch, curlopt url, $url); curl setopt($ch, curlopt returntransfer, true); curl setopt($ch, curlopt httpheader, \[ 'content type application/json', 'grpc metadata macaroon ' $this >macaroon ]); // for development only disable ssl verification // remove these in production and use proper certificates curl setopt($ch, curlopt ssl verifypeer, false); curl setopt($ch, curlopt ssl verifyhost, false); if ($method === 'post') { curl setopt($ch, curlopt post, true); if ($data !== null) { curl setopt($ch, curlopt postfields, json encode($data)); } } $response = curl exec($ch); $httpcode = curl getinfo($ch, curlinfo http code); $error = curl error($ch); curl close($ch); if ($error) { throw new exception("curl error " $error); } if ($httpcode >= 400) { throw new exception("http error " $httpcode " " $response); } return json decode($response, true); } public function get($endpoint) { return $this >executerequest('get', $endpoint); } public function post($endpoint, $data) { return $this >executerequest('post', $endpoint, $data); } } // initialize the client $lnd = new lndclient('your node host', 'your macaroon hex'); 2\ macaroon authentication macaroons can be provided either as hex encoded strings (common in environment variables) or read directly from macaroon files choose the method that best fits your deployment and security requirements file path best for local development, server deployments where you have direct file system access to macaroon files const fs = require('fs'); const host = process env lnd host; const macaroon path = process env macaroon path || '/path/to/admin macaroon'; // read macaroon from file and convert to hex const macaroon = fs readfilesync(macaroon path) tostring('hex'); // configure client with file based macaroon const lnd = axios create({ baseurl `https //${host} 8080`, headers { "content type" "application/json", "grpc metadata macaroon" macaroon } });import os import requests host = os getenv('lnd host') macaroon path = os getenv('macaroon path', '/path/to/admin macaroon') \# read macaroon from file and convert to hex with open(macaroon path, 'rb') as f macaroon = f read() hex() \# configure client with file based macaroon lnd = requests session() lnd headers update({ "content type" "application/json", "grpc metadata macaroon" macaroon }) lnd base url = f"https //{host} 8080"$host = $ env\['lnd host']; $macaroon path = $ env\['macaroon path'] ?? '/path/to/admin macaroon'; // read macaroon from file and convert to hex $macaroon = bin2hex(file get contents($macaroon path)); // configure client with file based macaroon $lnd = \[ 'base url' => "https //{$host} 8080", 'headers' => \[ "content type application/json", "grpc metadata macaroon {$macaroon}" ] ]; hex value best for production deployments, containerized environments, or when macaroons are stored in environment variables or secret management systems const host = process env lnd host; const macaroon = process env lnd macaroon; // hex string from environment // configure client with hex macaroon from env const lnd = axios create({ baseurl `https //${host} 8080`, headers { "content type" "application/json", "grpc metadata macaroon" macaroon } });import os import requests host = os getenv('lnd host') macaroon = os getenv('lnd macaroon') # hex string from environment \# configure client with hex macaroon from env lnd = requests session() lnd headers update({ "content type" "application/json", "grpc metadata macaroon" macaroon }) lnd base url = f"https //{host} 8080"$host = $ env\['lnd host']; $macaroon = $ env\['lnd macaroon']; // hex string from environment // configure client with hex macaroon from env $lnd = \[ 'base url' => "https //{$host} 8080", 'headers' => \[ "content type application/json", "grpc metadata macaroon {$macaroon}" ] ]; 3\ basic health check / node info verify your rest configuration by testing connectivity and retrieving basic node information const checknodestatus = async () => { try { const info = await lnd get("/v1/getinfo"); return info data; } catch (error) { console error("node status check failed ", error); throw error; } };async def check node status() try info = lnd get("/v1/getinfo") return info except requests exceptions requestexception as error print(f"node status check failed {error}") raise errorfunction checknodestatus($lnd) { try { $info = $lnd >get('/v1/getinfo'); return $info; } catch (exception $error) { error log("node status check failed " $error >getmessage()); throw $error; } } websocket configuration websockets provide real time, bidirectional communication for streaming data from your lnd node this interface is ideal for browser applications, dashboards, and any scenario requiring immediate notifications of node events websocket connections to lnd require the same macaroon authentication as rest but use persistent connections for continuous data streaming the connection url includes a method=get parameter to indicate streaming mode 1\ configure websocket client websocket clients connect to lnd's streaming endpoints using secure websocket connections (wss) with macaroon authentication in the connection headers const websocket = require('ws'); // configuration from environment variables const host = process env lnd host || 'localhost'; const macaroon = process env lnd macaroon; // hex encoded macaroon // create websocket client configuration const createwebsocketclient = (endpoint) => { // for streaming rpcs over rest, lnd expects the ?method=get query param const wsurl = `wss\ //${host} 8080${endpoint}?method=get`; return new websocket(wsurl, { rejectunauthorized false, // set to true if using trusted tls cert headers { 'grpc metadata macaroon' macaroon } }); }; // example connect to invoice subscription endpoint const ws = createwebsocketclient('/v1/invoices/subscribe');import websocket import json import os import ssl import threading \# configuration from environment variables host = os getenv('lnd host', 'localhost') macaroon = os getenv('lnd macaroon') # hex encoded macaroon def create websocket client(endpoint) """create a websocket client for lnd streaming endpoints """ \# for streaming rpcs over rest, lnd expects the ?method=get query param ws url = f"wss\ //{host} 8080{endpoint}?method=get" \# create ssl context that accepts self signed certificates ssl context = ssl create default context() ssl context check hostname = false ssl context verify mode = ssl cert none \# create websocket with authentication headers ws = websocket websocket(sslopt={"cert reqs" ssl cert none}) ws connect(ws url, header={ 'grpc metadata macaroon' macaroon }) return ws \# example connect to invoice subscription endpoint ws = create websocket client('/v1/invoices/subscribe')\<?php // using textalk/websocket php library (install via composer require textalk/websocket) use websocket\client; use websocket\connectionexception; use websocket\timeoutexception; // configuration from environment variables $host = $ env\['lnd host'] ?? 'localhost'; $macaroon = $ env\['lnd macaroon']; // hex encoded macaroon / create a websocket client for lnd streaming endpoints / function createwebsocketclient($endpoint) { global $host, $macaroon; // for streaming rpcs over rest, lnd expects the ?method=get query param $wsurl = "wss\ //{$host} 8080{$endpoint}?method=get"; // create client with authentication headers $client = new client($wsurl, \[ 'headers' => \[ 'grpc metadata macaroon' => $macaroon ], 'context' => stream context create(\[ 'ssl' => \[ 'verify peer' => false, // set to true if using trusted tls cert 'verify peer name' => false, 'allow self signed' => true ] ]), 'timeout' => 60 ]); return $client; } // example connect to invoice subscription endpoint $ws = createwebsocketclient('/v1/invoices/subscribe'); 2\ authentication setup websocket authentication uses the same macaroon system as rest but passes credentials through connection headers rather than per request headers // option 1 hex encoded macaroon from environment variable const setupwebsocketauthfromhex = () => { const macaroon hex = process env lnd macaroon; if (!macaroon hex) { throw new error('lnd macaroon environment variable not set'); } return { headers { 'grpc metadata macaroon' macaroon hex } }; }; // option 2 read macaroon from file const fs = require('fs'); const setupwebsocketauthfromfile = () => { const macaroon path = process env macaroon path || '/path/to/admin macaroon'; try { const macaroonbytes = fs readfilesync(macaroon path); const macaroonhex = macaroonbytes tostring('hex'); return { headers { 'grpc metadata macaroon' macaroonhex } }; } catch (error) { throw new error(`failed to read macaroon ${error message}`); } }; // create authenticated websocket connection const createauthenticatedwebsocket = (endpoint) => { const wsurl = `wss\ //${host} 8080${endpoint}?method=get`; const authoptions = setupwebsocketauthfromhex(); // or setupwebsocketauthfromfile() return new websocket(wsurl, { rejectunauthorized false, authoptions }); };# option 1 hex encoded macaroon from environment variable def setup websocket auth from hex() """setup websocket authentication using hex encoded macaroon from environment """ macaroon hex = os getenv('lnd macaroon') if not macaroon hex raise valueerror('lnd macaroon environment variable not set') return { 'header' { 'grpc metadata macaroon' macaroon hex } } \# option 2 read macaroon from file def setup websocket auth from file() """setup websocket authentication by reading macaroon from file """ macaroon path = os getenv('macaroon path', '/path/to/admin macaroon') try with open(macaroon path, 'rb') as f macaroon bytes = f read() macaroon hex = macaroon bytes hex() return { 'header' { 'grpc metadata macaroon' macaroon hex } } except exception as e raise valueerror(f'failed to read macaroon {e}') \# create authenticated websocket connection def create authenticated websocket(endpoint) """create an authenticated websocket connection to lnd """ ws url = f"wss\ //{host} 8080{endpoint}?method=get" auth options = setup websocket auth from hex() # or setup websocket auth from file() \# ssl context for self signed certificates ssl context = ssl create default context() ssl context check hostname = false ssl context verify mode = ssl cert none ws = websocket websocket(sslopt={"cert reqs" ssl cert none}) ws connect(ws url, auth options) return ws// option 1 hex encoded macaroon from environment variable function setupwebsocketauthfromhex() { $macaroonhex = $ env\['lnd macaroon'] ?? null; if (!$macaroonhex) { throw new exception('lnd macaroon environment variable not set'); } return \[ 'headers' => \[ 'grpc metadata macaroon' => $macaroonhex ] ]; } // option 2 read macaroon from file function setupwebsocketauthfromfile() { $macaroonpath = $ env\['macaroon path'] ?? '/path/to/admin macaroon'; try { $macaroonbytes = file get contents($macaroonpath); if ($macaroonbytes === false) { throw new exception("cannot read macaroon file"); } $macaroonhex = bin2hex($macaroonbytes); return \[ 'headers' => \[ 'grpc metadata macaroon' => $macaroonhex ] ]; } catch (exception $e) { throw new exception("failed to read macaroon " $e >getmessage()); } } // create authenticated websocket connection function createauthenticatedwebsocket($endpoint) { global $host; $wsurl = "wss\ //{$host} 8080{$endpoint}?method=get"; $authoptions = setupwebsocketauthfromhex(); // or setupwebsocketauthfromfile() // merge authentication with ssl options $options = array merge($authoptions, \[ 'context' => stream context create(\[ 'ssl' => \[ 'verify peer' => false, 'verify peer name' => false, 'allow self signed' => true ] ]), 'timeout' => 60 ]); return new client($wsurl, $options); } 3\ connection health check test your websocket configuration by establishing a connection and verifying proper authentication and data reception const testwebsocketconnection = async () => { return new promise((resolve, reject) => { const ws = createauthenticatedwebsocket('/v1/getinfo'); let connectiontimeout; // set connection timeout connectiontimeout = settimeout(() => { ws close(); reject(new error('websocket connection timeout')); }, 10000); // 10 second timeout ws on('open', () => { console log('ā
websocket connection established'); // for non streaming endpoints, send empty request ws send(json stringify({})); }); ws on('message', (data) => { cleartimeout(connectiontimeout); try { // rest proxy wraps response in { result } const response = json parse(data); const info = response result || response; console log('š” node info received '); console log(` ⢠alias ${info alias}`); console log(` ⢠version ${info version}`); console log(` ⢠chains ${info chains? join(', ')}`); console log(` ⢠block height ${info block height}`); ws close(); resolve(info); } catch (error) { ws close(); reject(new error(`failed to parse response ${error message}`)); } }); ws on('error', (error) => { cleartimeout(connectiontimeout); reject(new error(`websocket error ${error message}`)); }); ws on('close', () => { cleartimeout(connectiontimeout); console log('š websocket connection closed'); }); }); };def test websocket connection() """test websocket connection and authentication """ try \# create connection with timeout ws = create authenticated websocket('/v1/getinfo') ws settimeout(10) # 10 second timeout print('ā
websocket connection established') \# for non streaming endpoints, send empty request ws send(json dumps({})) \# receive response response data = ws recv() \# rest proxy wraps response in { result } response = json loads(response data) info = response get('result', response) print('š” node info received ') print(f" ⢠alias {info get('alias', 'n/a')}") print(f" ⢠version {info get('version', 'n/a')}") print(f" ⢠chains {', ' join(info get('chains', \[]))}") print(f" ⢠block height {info get('block height', 'n/a')}") ws close() print('š websocket connection closed') return info except websocket websockettimeoutexception raise exception('websocket connection timeout') except exception as e raise exception(f'websocket error {e}')function testwebsocketconnection() { try { $ws = createauthenticatedwebsocket('/v1/getinfo'); echo "ā
websocket connection established\n"; // for non streaming endpoints, send empty request $ws >send(json encode(new stdclass())); // receive response with timeout $ws >settimeout(10); // 10 second timeout $responsedata = $ws >receive(); // rest proxy wraps response in { result } $response = json decode($responsedata, true); $info = $response\['result'] ?? $response; echo "š” node info received \n"; echo " ⢠alias " ($info\['alias'] ?? 'n/a') "\n"; echo " ⢠version " ($info\['version'] ?? 'n/a') "\n"; echo " ⢠chains " implode(', ', $info\['chains'] ?? \[]) "\n"; echo " ⢠block height " ($info\['block height'] ?? 'n/a') "\n"; $ws >close(); echo "š websocket connection closed\n"; return $info; } catch (timeoutexception $e) { throw new exception('websocket connection timeout'); } catch (connectionexception $e) { throw new exception('websocket error ' $e >getmessage()); } catch (exception $e) { throw new exception('failed to parse response ' $e >getmessage()); } } grpc configuration grpc provides the most efficient and feature complete interface to lnd, offering strongly typed responses, bidirectional streaming, and the lowest latency communication this interface is ideal for production applications requiring high performance and reliability grpc clients require proto file definitions, ssl certificate handling, and metadata based authentication while more complex to set up than rest, grpc offers superior performance and access to the complete lnd api surface 1\ setup and dependencies grpc requires proto file definitions and specific client libraries for each language download the appropriate proto files and install required dependencies before configuring your client // install required dependencies // npm install @grpc/grpc js @grpc/proto loader // download proto files for your lnd version // curl o lightning proto https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/lightning proto // curl o router proto https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/routerrpc/router proto // curl o invoices proto https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices proto const fs = require('fs'); const grpc = require('@grpc/grpc js'); const protoloader = require('@grpc/proto loader'); const path = require('path'); // proto file paths const proto path = process env proto path || ' /protos/lightning proto'; const router proto path = process env router proto path || ' /protos/router proto'; const invoices proto path = process env invoices proto path || ' /protos/invoices proto'; // grpc loader options for consistent proto handling const loaderoptions = { keepcase true, longs string, enums string, defaults true, oneofs true }; // load proto definitions const lnrpcpackagedefinition = protoloader loadsync(proto path, loaderoptions); const routerpackagedefinition = protoloader loadsync(router proto path, loaderoptions); const invoicespackagedefinition = protoloader loadsync(invoices proto path, loaderoptions);""" install required dependencies pip install grpcio grpcio tools protobuf download proto files for your lnd version wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/lightning proto wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/routerrpc/router proto wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices proto compile proto files python m grpc tools protoc proto path= python out= grpc python out= lightning proto python m grpc tools protoc proto path= python out= grpc python out= routerrpc/router proto python m grpc tools protoc proto path= python out= grpc python out= invoicesrpc/invoices proto """ import os import sys import grpc import codecs from pathlib import path \# add proto files to python path sys path insert(0, os path dirname( file )) \# import generated proto modules import lightning pb2 as lnrpc import lightning pb2 grpc as lnrpc grpc import routerrpc router pb2 as router pb2 import routerrpc router pb2 grpc as router pb2 grpc import invoicesrpc invoices pb2 as invoices pb2 import invoicesrpc invoices pb2 grpc as invoices pb2 grpc \# configuration from environment variables lnd host = os getenv('lnd host', 'localhost') lnd port = os getenv('lnd port', '10009') macaroon path = os getenv('macaroon path', '/path/to/admin macaroon') tls cert path = os getenv('tls cert path', '/path/to/tls cert')\<?php // 1 setup and dependencies / install required dependencies composer require grpc/grpc composer require google/protobuf install php grpc extension pecl install grpc download proto files for your lnd version wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/lightning proto wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/routerrpc/router proto wget https //raw\ githubusercontent com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices proto compile proto files protoc php out= grpc out= plugin=protoc gen grpc=`which grpc php plugin` lightning proto protoc php out= grpc out= plugin=protoc gen grpc=`which grpc php plugin` routerrpc/router proto protoc php out= grpc out= plugin=protoc gen grpc=`which grpc php plugin` invoicesrpc/invoices proto / require once dir '/vendor/autoload php'; // import generated proto classes require once dir '/lnrpc/lightningclient php'; require once dir '/lnrpc/getinforequest php'; require once dir '/lnrpc/getinforesponse php'; require once dir '/lnrpc/walletbalancerequest php'; require once dir '/lnrpc/walletbalanceresponse php'; require once dir '/lnrpc/listinvoicerequest php'; require once dir '/lnrpc/listinvoiceresponse php'; require once dir '/lnrpc/invoicesubscription php'; require once dir '/lnrpc/invoice php'; require once dir '/routerrpc/routerclient php'; require once dir '/invoicesrpc/invoicesclient php'; use lnrpc\lightningclient; use lnrpc\getinforequest; use lnrpc\walletbalancerequest; use lnrpc\listinvoicerequest; use lnrpc\invoicesubscription; use routerrpc\routerclient; use invoicesrpc\invoicesclient; use grpc\channelcredentials; // configuration from environment variables $lnd host = $ env\['lnd host'] ?? 'localhost'; $lnd port = $ env\['lnd port'] ?? '10009'; 2\ configure grpc client create a grpc client with proper ssl credentials and macaroon authentication metadata the client handles both unary calls and streaming rpcs const fs = require('fs'); const grpc = require('@grpc/grpc js'); const protoloader = require('@grpc/proto loader'); // configuration from environment variables const lnd host = process env lnd host || 'localhost'; const lnd port = process env lnd port || '10009'; const macaroon path = process env macaroon path || '/path/to/admin macaroon'; const tls cert path = process env tls cert path || '/path/to/tls cert'; // create grpc client factory function createlndclient(service = 'lightning') { // enable proper ssl cipher suites for lnd process env grpc ssl cipher suites = 'high+ecdsa'; // read macaroon and tls certificate let macaroon; try { macaroon = fs readfilesync(macaroon path) tostring('hex'); } catch (error) { throw new error(`failed to read macaroon ${error message}`); } // create ssl credentials let sslcreds; if (tls cert path && fs existssync(tls cert path)) { // use custom tls certificate if provided const tlscert = fs readfilesync(tls cert path); sslcreds = grpc credentials createssl(tlscert); } else { // use default ssl without custom cert (for remote connections) sslcreds = grpc credentials createssl(); } // create macaroon credentials const metadata = new grpc metadata(); metadata add('macaroon', macaroon); const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); // combine ssl and macaroon credentials const credentials = grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); // load proto and create client based on service type const protopath = { 'lightning' ' /protos/lightning proto', 'router' ' /protos/router proto', 'invoices' ' /protos/invoices proto' }\[service]; const packagedefinition = protoloader loadsync(protopath, { keepcase true, longs string, enums string, defaults true, oneofs true }); const lnrpcdescriptor = grpc loadpackagedefinition(packagedefinition); // get the appropriate service namespace let serviceclient; switch(service) { case 'lightning' serviceclient = lnrpcdescriptor lnrpc lightning; break; case 'router' serviceclient = lnrpcdescriptor routerrpc router; break; case 'invoices' serviceclient = lnrpcdescriptor invoicesrpc invoices; break; default throw new error(`unknown service ${service}`); } // create and return the client return new serviceclient( `${lnd host} ${lnd port}`, credentials ); } // export configured clients module exports = { lightning createlndclient('lightning'), router createlndclient('router'), invoices createlndclient('invoices') };class lndclient """create grpc client factory for lnd services""" def init (self, host=none, port=none, macaroon path=none, tls cert path=none) self host = host or lnd host self port = port or lnd port self macaroon path = macaroon path or macaroon path self tls cert path = tls cert path or tls cert path \# create credentials self credentials = self create credentials() \# create channel self channel = grpc secure channel( f'{self host} {self port}', self credentials, options=\[ ('grpc max receive message length', 33554432), # 32mb ('grpc max send message length', 33554432), ] ) \# create service stubs self lightning = lnrpc grpc lightningstub(self channel) self router = router pb2 grpc routerstub(self channel) self invoices = invoices pb2 grpc invoicesstub(self channel) def create credentials(self) """create combined ssl and macaroon credentials""" \# read macaroon try with open(self macaroon path, 'rb') as f macaroon bytes = f read() macaroon hex = codecs encode(macaroon bytes, 'hex') except exception as e raise exception(f"failed to read macaroon {e}") \# create ssl credentials if self tls cert path and os path exists(self tls cert path) \# use custom tls certificate if provided with open(self tls cert path, 'rb') as f cert bytes = f read() ssl creds = grpc ssl channel credentials(cert bytes) else \# use default ssl without custom cert (for remote connections) ssl creds = grpc ssl channel credentials() \# create metadata callback for macaroon def metadata callback(context, callback) callback(\[('macaroon', macaroon hex)], none) \# create macaroon credentials macaroon creds = grpc metadata call credentials(metadata callback) \# combine credentials return grpc composite channel credentials(ssl creds, macaroon creds) def close(self) """close the grpc channel""" self channel close() \# create client instances def create lnd client(service='lightning', kwargs) """factory function to create lnd client for specific service""" client = lndclient( kwargs) if service == 'lightning' return client lightning elif service == 'router' return client router elif service == 'invoices' return client invoices else raise valueerror(f"unknown service {service}") \# export configured clients (singleton pattern) client instance = none def get client() """get singleton lnd client instance""" global client instance if client instance is none client instance = lndclient() return client instanceclass lndclient { private $host; private $port; private $macaroonpath; private $tlscertpath; private $credentials; private $lightning; private $router; private $invoices; / create grpc client factory for lnd services / public function construct($host = null, $port = null, $macaroonpath = null, $tlscertpath = null) { global $lnd host, $lnd port, $macaroon path, $tls cert path; $this >host = $host ?? $lnd host; $this >port = $port ?? $lnd port; $this >macaroonpath = $macaroonpath ?? $macaroon path; $this >tlscertpath = $tlscertpath ?? $tls cert path; // create credentials $this >credentials = $this >createcredentials(); // create service clients $this >createserviceclients(); } / create combined ssl and macaroon credentials / private function createcredentials() { // read macaroon if (!file exists($this >macaroonpath)) { throw new exception("macaroon file not found {$this >macaroonpath}"); } $macaroonbytes = file get contents($this >macaroonpath); if ($macaroonbytes === false) { throw new exception("failed to read macaroon file"); } $macaroonhex = bin2hex($macaroonbytes); // create ssl credentials if ($this >tlscertpath && file exists($this >tlscertpath)) { // use custom tls certificate if provided $certbytes = file get contents($this >tlscertpath); if ($certbytes === false) { throw new exception("failed to read tls certificate"); } $sslcreds = channelcredentials createssl($certbytes); } else { // use default ssl without custom cert (for remote connections) $sslcreds = channelcredentials createssl(); } // create metadata for macaroon $metadata = \[ 'macaroon' => \[$macaroonhex] ]; return \[ 'credentials' => $sslcreds, 'metadata' => $metadata ]; } / create service clients with authentication / private function createserviceclients() { $endpoint = "{$this >host} {$this >port}"; $opts = \[ 'credentials' => $this >credentials\['credentials'], 'update metadata' => function($metadata) { foreach ($this >credentials\['metadata'] as $key => $value) { $metadata\[$key] = $value; } return $metadata; } ]; // create lightning client $this >lightning = new lightningclient($endpoint, $opts); // create router client $this >router = new routerclient($endpoint, $opts); // create invoices client $this >invoices = new invoicesclient($endpoint, $opts); } / get lightning service client / public function getlightning() { return $this >lightning; } / get router service client / public function getrouter() { return $this >router; } / get invoices service client / public function getinvoices() { return $this >invoices; } } // factory function to create lnd client for specific service function createlndclient($service = 'lightning', $host = null, $port = null, $macaroonpath = null, $tlscertpath = null) { $client = new lndclient($host, $port, $macaroonpath, $tlscertpath); switch ($service) { case 'lightning' return $client >getlightning(); case 'router' return $client >getrouter(); case 'invoices' return $client >getinvoices(); default throw new invalidargumentexception("unknown service {$service}"); } } // singleton pattern for client instance class lndclientsingleton { private static $instance = null; public static function getclient() { if (self $instance === null) { self $instance = new lndclient(); } return self $instance; } } 3\ authentication and metadata grpc authentication uses metadata to pass macaroon credentials with each request configure your client to automatically include authentication metadata for all calls // authentication helper utilities for grpc calls const fs = require('fs'); const grpc = require('@grpc/grpc js'); // create metadata with macaroon for authenticated calls function createauthmetadata(macaroonpath) { const metadata = new grpc metadata(); try { // read macaroon from file const macaroonbytes = fs readfilesync(macaroonpath); const macaroonhex = macaroonbytes tostring('hex'); // add macaroon to metadata metadata add('macaroon', macaroonhex); return metadata; } catch (error) { throw new error(`failed to create auth metadata ${error message}`); } } // create credentials with custom macaroon permissions function createcustomcredentials(options = {}) { const { macaroonpath = process env macaroon path, tlscertpath = process env tls cert path, macaroonhex = null // option to provide pre encoded macaroon } = options; // set up ssl credentials let sslcreds; if (tlscertpath && fs existssync(tlscertpath)) { const tlscert = fs readfilesync(tlscertpath); sslcreds = grpc credentials createssl(tlscert); } else { sslcreds = grpc credentials createssl(); } // set up macaroon credentials const metadata = new grpc metadata(); if (macaroonhex) { // use provided hex encoded macaroon metadata add('macaroon', macaroonhex); } else if (macaroonpath) { // read from file const macaroonbytes = fs readfilesync(macaroonpath); metadata add('macaroon', macaroonbytes tostring('hex')); } else { throw new error('either macaroonpath or macaroonhex must be provided'); } const macarooncreds = grpc credentials createfrommetadatagenerator( ( args, callback) => callback(null, metadata) ); // combine credentials return grpc credentials combinechannelcredentials( sslcreds, macarooncreds ); } // wrapper for making authenticated unary calls async function makeauthenticatedcall(client, method, request, macaroonpath) { const metadata = createauthmetadata(macaroonpath); return new promise((resolve, reject) => { client\[method]\(request, metadata, (error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); } // wrapper for authenticated streaming calls function createauthenticatedstream(client, method, request, macaroonpath) { const metadata = createauthmetadata(macaroonpath); return client\[method]\(request, metadata); } // usage examples // example 1 using different macaroon types const readonlycredentials = createcustomcredentials({ macaroonpath '/path/to/readonly macaroon' }); const invoicecredentials = createcustomcredentials({ macaroonpath '/path/to/invoice macaroon' }); // example 2 making authenticated calls with wrapper async function getnodeinfo(client) { try { const info = await makeauthenticatedcall( client, 'getinfo', {}, process env macaroon path ); return info; } catch (error) { console error('failed to get node info ', error message); throw error; } } // example 3 creating authenticated stream function subscribetoinvoices(client) { const stream = createauthenticatedstream( client, 'subscribeinvoices', { add index '0', settle index '0' }, process env macaroon path ); return stream; } module exports = { createauthmetadata, createcustomcredentials, makeauthenticatedcall, createauthenticatedstream };"""authentication helper utilities for grpc calls""" def create auth metadata(macaroon path) """create metadata with macaroon for authenticated calls""" try \# read macaroon from file with open(macaroon path, 'rb') as f macaroon bytes = f read() macaroon hex = codecs encode(macaroon bytes, 'hex') \# return metadata tuple return \[('macaroon', macaroon hex)] except exception as e raise exception(f"failed to create auth metadata {e}") def create custom credentials(macaroon path=none, tls cert path=none, macaroon hex=none) """create credentials with custom macaroon permissions""" macaroon path = macaroon path or os getenv('macaroon path') tls cert path = tls cert path or os getenv('tls cert path') \# set up ssl credentials if tls cert path and os path exists(tls cert path) with open(tls cert path, 'rb') as f cert bytes = f read() ssl creds = grpc ssl channel credentials(cert bytes) else ssl creds = grpc ssl channel credentials() \# set up macaroon credentials if macaroon hex \# use provided hex encoded macaroon macaroon data = macaroon hex elif macaroon path \# read from file with open(macaroon path, 'rb') as f macaroon bytes = f read() macaroon data = codecs encode(macaroon bytes, 'hex') else raise valueerror('either macaroon path or macaroon hex must be provided') \# create metadata callback def metadata callback(context, callback) callback(\[('macaroon', macaroon data)], none) macaroon creds = grpc metadata call credentials(metadata callback) \# combine credentials return grpc composite channel credentials(ssl creds, macaroon creds) \# wrapper for making authenticated unary calls def make authenticated call(client, method, request, macaroon path) """make an authenticated unary call""" metadata = create auth metadata(macaroon path) \# get the method from the client rpc method = getattr(client, method) \# make the call with metadata return rpc method(request, metadata=metadata) \# wrapper for authenticated streaming calls def create authenticated stream(client, method, request, macaroon path) """create an authenticated streaming call""" metadata = create auth metadata(macaroon path) \# get the method from the client rpc method = getattr(client, method) \# return the stream with metadata return rpc method(request, metadata=metadata)/ authentication helper utilities for grpc calls / / create metadata with macaroon for authenticated calls / function createauthmetadata($macaroonpath) { try { // read macaroon from file $macaroonbytes = file get contents($macaroonpath); if ($macaroonbytes === false) { throw new exception("cannot read macaroon file"); } $macaroonhex = bin2hex($macaroonbytes); // return metadata array return \[ 'macaroon' => \[$macaroonhex] ]; } catch (exception $e) { throw new exception("failed to create auth metadata " $e >getmessage()); } } / create credentials with custom macaroon permissions / function createcustomcredentials($macaroonpath = null, $tlscertpath = null, $macaroonhex = null) { $macaroonpath = $macaroonpath ?? $ env\['macaroon path'] ?? null; $tlscertpath = $tlscertpath ?? $ env\['tls cert path'] ?? null; // set up ssl credentials if ($tlscertpath && file exists($tlscertpath)) { $certbytes = file get contents($tlscertpath); if ($certbytes === false) { throw new exception("failed to read tls certificate"); } $sslcreds = channelcredentials createssl($certbytes); } else { $sslcreds = channelcredentials createssl(); } // set up macaroon credentials if ($macaroonhex) { // use provided hex encoded macaroon $macaroondata = $macaroonhex; } elseif ($macaroonpath) { // read from file $macaroonbytes = file get contents($macaroonpath); if ($macaroonbytes === false) { throw new exception("failed to read macaroon file"); } $macaroondata = bin2hex($macaroonbytes); } else { throw new exception('either macaroonpath or macaroonhex must be provided'); } // return credentials configuration return \[ 'credentials' => $sslcreds, 'metadata' => \[ 'macaroon' => \[$macaroondata] ] ]; } / wrapper for making authenticated unary calls / function makeauthenticatedcall($client, $method, $request, $macaroonpath) { $metadata = createauthmetadata($macaroonpath); // create call options with metadata $options = \[ 'metadata' => $metadata ]; // make the call list($response, $status) = $client >$method($request, $options) >wait(); if ($status >code !== grpc\status ok) { throw new exception("grpc call failed " $status >details); } return $response; } / wrapper for authenticated streaming calls / function createauthenticatedstream($client, $method, $request, $macaroonpath) { $metadata = createauthmetadata($macaroonpath); // create call options with metadata $options = \[ 'metadata' => $metadata ]; // return the stream return $client >$method($request, $options); } 4\ health check verify your grpc configuration by making a simple unary call to retrieve node information and confirm proper authentication and connectivity const grpc = require('@grpc/grpc js'); const protoloader = require('@grpc/proto loader'); const { createlndclient } = require(' /grpc client'); // comprehensive health check for grpc connection async function checkgrpcconnection() { console log('š starting grpc health check \n'); try { // create lightning client const lightning = createlndclient('lightning'); // test 1 get basic node info console log('1ļøā£ testing getinfo '); const info = await new promise((resolve, reject) => { lightning getinfo({}, (error, response) => { if (error) reject(error); else resolve(response); }); }); console log('ā
getinfo successful '); console log(` ⢠alias ${info alias}`); console log(` ⢠version ${info version}`); console log(` ⢠chains ${info chains? join(', ')}`); console log(` ⢠block height ${info block height}`); console log(` ⢠synced to chain ${info synced to chain}`); console log(` ⢠active channels ${info num active channels}`); console log(` ⢠peers ${info num peers}\n`); // test 2 check wallet balance console log('2ļøā£ testing walletbalance '); const balance = await new promise((resolve, reject) => { lightning walletbalance({}, (error, response) => { if (error) reject(error); else resolve(response); }); }); console log('ā
walletbalance successful '); console log(` ⢠total balance ${balance total balance} sats`); console log(` ⢠confirmed ${balance confirmed balance} sats`); console log(` ⢠unconfirmed ${balance unconfirmed balance} sats\n`); // test 3 list recent invoices (limited to 5) console log('3ļøā£ testing listinvoices '); const invoices = await new promise((resolve, reject) => { lightning listinvoices({ num max invoices 5, reversed true }, (error, response) => { if (error) reject(error); else resolve(response); }); }); console log('ā
listinvoices successful '); console log(` ⢠total invoices ${invoices invoices length}`); invoices invoices foreach((inv, idx) => { console log(` ⢠invoice ${idx + 1} ${inv state} ${inv value} sats`); }); console log(''); // test 4 test streaming subscription (brief) console log('4ļøā£ testing streaming subscription '); const stream = lightning subscribeinvoices({ add index '0', settle index '0' }); // set up timeout for streaming test const streamtimeout = settimeout(() => { stream cancel(); }, 2000); // test for 2 seconds await new promise((resolve, reject) => { stream on('data', (invoice) => { console log('ā
received invoice update via stream'); cleartimeout(streamtimeout); stream cancel(); resolve(); }); stream on('error', (error) => { if (error code !== grpc status cancelled) { cleartimeout(streamtimeout); reject(error); } }); stream on('end', () => { cleartimeout(streamtimeout); console log('ā
stream test completed'); resolve(); }); // if no data received, still consider it successful if no errors settimeout(() => { console log('ā
stream connection established (no new invoices)'); resolve(); }, 1500); }); console log('\nš all grpc health checks passed!\n'); return { success true, nodeinfo info, balance balance }; } catch (error) { console error('\nā grpc health check failed ', error message); console error('error details ', error); // provide troubleshooting hints console log('\nš” troubleshooting tips '); console log('1 check lnd host and lnd port environment variables'); console log('2 verify macaroon file path and permissions'); console log('3 ensure tls certificate is valid (if using custom cert)'); console log('4 confirm lnd node is running and accessible'); console log('5 check firewall rules for port 10009'); return { success false, error error message }; } } // minimal health check for monitoring/heartbeat async function quickhealthcheck(lightning) { try { const info = await new promise((resolve, reject) => { const timeout = settimeout(() => { reject(new error('health check timeout')); }, 5000); lightning getinfo({}, (error, response) => { cleartimeout(timeout); if (error) reject(error); else resolve(response); }); }); return { healthy true, synced info synced to chain, blockheight info block height, activechannels info num active channels }; } catch (error) { return { healthy false, error error message }; } } // run health check if called directly if (require main === module) { checkgrpcconnection() then(result => { process exit(result success ? 0 1); }) catch(error => { console error('unexpected error ', error); process exit(1); }); } module exports = { checkgrpcconnection, quickhealthcheck };import time import asyncio from concurrent futures import timeouterror def check grpc connection() """comprehensive health check for grpc connection""" print('š starting grpc health check \n') try \# create lightning client client = get client() lightning = client lightning \# test 1 get basic node info print('1ļøā£ testing getinfo ') info = lightning getinfo(lnrpc getinforequest()) print('ā
getinfo successful ') print(f' ⢠alias {info alias}') print(f' ⢠version {info version}') print(f' ⢠chains {", " join(info chains)}') print(f' ⢠block height {info block height}') print(f' ⢠synced to chain {info synced to chain}') print(f' ⢠active channels {info num active channels}') print(f' ⢠peers {info num peers}\n') \# test 2 check wallet balance print('2ļøā£ testing walletbalance ') balance = lightning walletbalance(lnrpc walletbalancerequest()) print('ā
walletbalance successful ') print(f' ⢠total balance {balance total balance} sats') print(f' ⢠confirmed {balance confirmed balance} sats') print(f' ⢠unconfirmed {balance unconfirmed balance} sats\n') \# test 3 list recent invoices (limited to 5) print('3ļøā£ testing listinvoices ') invoices response = lightning listinvoices( lnrpc listinvoicerequest( num max invoices=5, reversed=true ) ) print('ā
listinvoices successful ') print(f' ⢠total invoices {len(invoices response invoices)}') for idx, inv in enumerate(invoices response invoices) state name = lnrpc invoice invoicestate name(inv state) print(f' ⢠invoice {idx + 1} {state name} {inv value} sats') print('') \# test 4 test streaming subscription (brief) print('4ļøā£ testing streaming subscription ') \# create subscription request = lnrpc invoicesubscription( add index=0, settle index=0 ) \# test stream for 2 seconds stream = lightning subscribeinvoices(request) stream tested = false try \# set timeout for stream test start time = time time() for invoice in stream print('ā
received invoice update via stream') stream tested = true break \# if no data after 2 seconds, still consider successful if time time() start time > 2 0 and not stream tested print('ā
stream connection established (no new invoices)') stream tested = true except grpc rpcerror as e if e code() != grpc statuscode cancelled raise finally \# cancel the stream stream cancel() if stream tested print('ā
stream test completed') print('\nš all grpc health checks passed!\n') return { 'success' true, 'node info' info, 'balance' balance } except exception as e print(f'\nā grpc health check failed {str(e)}') print(f'error details {e}') \# provide troubleshooting hints print('\nš” troubleshooting tips ') print('1 check lnd host and lnd port environment variables') print('2 verify macaroon file path and permissions') print('3 ensure tls certificate is valid (if using custom cert)') print('4 confirm lnd node is running and accessible') print('5 check firewall rules for port 10009') return { 'success' false, 'error' str(e) } \# run health check if called directly if name == ' main ' result = check grpc connection() sys exit(0 if result\['success'] else 1)/ comprehensive health check for grpc connection / function checkgrpcconnection() { echo "š starting grpc health check \n\n"; try { // create lightning client $client = lndclientsingleton getclient(); $lightning = $client >getlightning(); // test 1 get basic node info echo "1ļøā£ testing getinfo \n"; list($info, $status) = $lightning >getinfo(new getinforequest()) >wait(); if ($status >code !== grpc\status ok) { throw new exception("getinfo failed " $status >details); } echo "ā
getinfo successful \n"; echo " ⢠alias " $info >getalias() "\n"; echo " ⢠version " $info >getversion() "\n"; echo " ⢠chains " implode(', ', iterator to array($info >getchains())) "\n"; echo " ⢠block height " $info >getblockheight() "\n"; echo " ⢠synced to chain " ($info >getsyncedtochain() ? 'true' 'false') "\n"; echo " ⢠active channels " $info >getnumactivechannels() "\n"; echo " ⢠peers " $info >getnumpeers() "\n\n"; // test 2 check wallet balance echo "2ļøā£ testing walletbalance \n"; list($balance, $status) = $lightning >walletbalance(new walletbalancerequest()) >wait(); if ($status >code !== grpc\status ok) { throw new exception("walletbalance failed " $status >details); } echo "ā
walletbalance successful \n"; echo " ⢠total balance " $balance >gettotalbalance() " sats\n"; echo " ⢠confirmed " $balance >getconfirmedbalance() " sats\n"; echo " ⢠unconfirmed " $balance >getunconfirmedbalance() " sats\n\n"; // test 3 list recent invoices (limited to 5) echo "3ļøā£ testing listinvoices \n"; $invoicesrequest = new listinvoicerequest(); $invoicesrequest >setnummaxinvoices(5); $invoicesrequest >setreversed(true); list($invoicesresponse, $status) = $lightning >listinvoices($invoicesrequest) >wait(); if ($status >code !== grpc\status ok) { throw new exception("listinvoices failed " $status >details); } echo "ā
listinvoices successful \n"; $invoices = $invoicesresponse >getinvoices(); echo " ⢠total invoices " count($invoices) "\n"; foreach ($invoices as $idx => $inv) { $statename = getinvoicestatename($inv >getstate()); echo " ⢠invoice " ($idx + 1) " " $statename " " $inv >getvalue() " sats\n"; } echo "\n"; // test 4 test streaming subscription (brief) echo "4ļøā£ testing streaming subscription \n"; // create subscription $request = new invoicesubscription(); $request >setaddindex('0'); $request >setsettleindex('0'); // test stream for 2 seconds $stream = $lightning >subscribeinvoices($request); $streamtested = false; try { // set timeout for stream test $starttime = time(); while ($invoice = $stream >read()) { echo "ā
received invoice update via stream\n"; $streamtested = true; break; } // if no data after 2 seconds, still consider successful if (time() $starttime > 2 && !$streamtested) { echo "ā
stream connection established (no new invoices)\n"; $streamtested = true; } } catch (exception $e) { // ignore cancellation errors if (strpos($e >getmessage(), 'cancelled') === false) { throw $e; } } finally { // cancel the stream $stream >cancel(); if ($streamtested) { echo "ā
stream test completed\n"; } } echo "\nš all grpc health checks passed!\n\n"; return \[ 'success' => true, 'node info' => $info, 'balance' => $balance ]; } catch (exception $e) { echo "\nā grpc health check failed " $e >getmessage() "\n"; echo "error details " print r($e, true) "\n"; // provide troubleshooting hints echo "\nš” troubleshooting tips \n"; echo "1 check lnd host and lnd port environment variables\n"; echo "2 verify macaroon file path and permissions\n"; echo "3 ensure tls certificate is valid (if using custom cert)\n"; echo "4 confirm lnd node is running and accessible\n"; echo "5 check firewall rules for port 10009\n"; return \[ 'success' => false, 'error' => $e >getmessage() ]; } } // run health check if called directly if (php sapi name() === 'cli' && basename( file ) === basename($ server\['php self'])) { $result = checkgrpcconnection(); exit($result\['success'] ? 0 1); } implementation notes environment variables store sensitive credentials like lnd host and lnd macaroon in environment variables rather than hardcoding them use a env file for local development and proper secret management for production tls verification lnd uses self signed certificates by default for production, either use proper tls certificates or configure your clients to accept self signed certificates securely grpc clients require explicit ssl credential configuration macaroon types use the most restrictive macaroon possible for your use case for read only operations, use readonly macaroon for invoice creation, invoice macaroon provides sufficient permissions without full admin access interface selection choose rest for simple integrations and development, websockets for real time browser applications, and grpc for high performance production systems requiring the full lnd feature set