---
title: Configuration
slug: configuration
docTags: 
createdAt: 2025-06-04T17:48:19.236Z
---

# 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.

![](https://api.archbee.com/api/optimize/WCn7j8shlcs6zTGZDBCrx-S_qRV6NPxYXwEwNkNC4pO-20250613-074311.svg)

## 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.

:::CodeblockTabs
```javascript
const lnd = axios.create({
  baseURL: https://${HOST}:8080,
  headers: {
    "Content-Type": "application/json",
    "Grpc-Metadata-Macaroon": MACAROON
  }
});
```

```python
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")
```

```php
// 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.

:::CodeblockTabs
```javascript
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
  }
});
```

```python
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"
```

```php
$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.

:::CodeblockTabs
```javascript
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
  }
});
```

```python
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"
```

```php
$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.

:::CodeblockTabs
```javascript
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;
  }
};
```

```python
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 error
```

```php
function 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.

:::CodeblockTabs
```javascript
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');
```

```python
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
<?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.

:::CodeblockTabs
```javascript
// 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
  });
};
```

```python
# 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
```

```php
// 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.

:::CodeblockTabs
```javascript
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');
    });
  });
};
```

```python
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}')
```

```php
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.

:::CodeblockTabs
```javascript
// 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);
```

```python
"""
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
<?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.

:::CodeblockTabs
```javascript
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')
};
```

```python
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_instance
```

```php
class 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.

:::CodeblockTabs
```javascript
// 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
};
```

```python
"""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)
```

```php
/**
 * 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.

:::CodeblockTabs
```javascript
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
};
```

```python
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)
```

```php
/**
 * 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.



