Main Menu
Examples
Host your own Lightning Address Server (NodeJS)
18 min
introduction lightning addresses allow you to receive bitcoin over lightning using an email like identifier (e g alice\@yourdomain com) this tutorial shows you how to host your own lightning address server using a voltage cloud node and lnd’s rest api — eliminating the complexity of grpc by leveraging voltage’s rest endpoint and a simple node js server using axios, you can generate invoices dynamically and serve a lightning address with minimal setup lightning address spec lightning address is a part of the lnurl spec in order to support lightning address you must support the 3 specs mentioned below https //github com/lnurl/luds/blob/luds/01 md https //github com/lnurl/luds/blob/luds/01 md https //github com/lnurl/luds/blob/luds/06 md https //github com/lnurl/luds/blob/luds/06 md https //github com/lnurl/luds/blob/luds/16 md https //github com/lnurl/luds/blob/luds/16 md why use rest instead of grpc? using rest avoids the need to manage tls certs, proto files, and grpc libraries voltage’s cloud interface provides a clean https rest endpoint with macaroon based auth, making it easy to call from any http client rest is ideal for lightweight server setups quickstart checklist before coding, ensure you have voltage lnd node with rest api access and inbound liquidity domain name with https a public https domain is required for lightning address support node js environment install node js (v14+) and npm lnd rest credentials rest host url e g https //\<nodeid> m voltageapp io 8080 macaroon (hex) export a hex encoded macaroon with invoice permission environment config create a env file with the necessary values lnd rest host, lnd rest macaroon hex, and your domain we’ll provide a template in the setup section once you have all the above, the setup process will involve installing a few npm packages and writing the node js server code then you’ll run the server and test receiving a lightning payment via your new lightning address let’s get started! setup and configuration 1\ obtain lnd rest url and macaroon log in to your voltage account and navigate to your node’s connect info you should find the rest url and admin macaroon (or invoice macaroon) available for download or copyvoltage cloud the rest url will look something like take note of this url – we’ll use it as lnd rest host in our config next, get the macaroon in hex format if voltage provides a base64 or binary macaroon file (e g admin macaroon), convert it to hex by reading it in node or using a tool for example, in node you can do const fs = require('fs'); const macaroonhex = fs readfilesync(' /admin macaroon') tostring('hex'); in many cases, voltage might display the hex directly if you prefer a least privilege approach, create an invoice only macaroon using thunderhub (or lnd cli), bake a macaroon with create invoices permission onlygithub com copy the hex string of that macaroon for the next step 2\ configure environment variables ( env) in your project directory, create a file named env and add the following variables \# env lightning address server config lnd rest host=https //\<your lnd rest url> 8080 lnd rest macaroon hex=\<your invoice macaroon hex> domain=\<your domain name> replace \<your lnd rest url> with the host from step 1 (for voltage this is the voltageapp io url or an onion address if applicable), \<your invoice macaroon hex> with the hex encoded macaroon, and \<your domain name> with your domain (e g mydomain com) where this service will rungithub com these values will be loaded by our node js server lnd rest host – base url for lnd’s rest api (including https // and port, usually 8080 for lnd rest)github com lnd rest macaroon hex – hex string of the macaroon that allows invoice creationgithub com domain – the domain name you’re using for the lightning address (used to construct callback urls and metadata)github com note if your lnd node uses a self signed certificate (default for lnd), our code will disable strict tls verification for simplicity in production, consider properly trusting the tls certificate or using a certificate signed by a known ca to avoid disabling verification 3\ install dependencies initialize a new node js project and install the required packages $ npm init y $ npm install express axios dotenv these will give us express to quickly set up http server routes axios to make http requests to lnd’s rest api dotenv to load the config from our env file 4\ plan the server routes we’ll follow the lnurl pay flow, which requires discovery endpoint responds with payment metadata and a callback url callback endpoint handles invoice creation requires amount in millisatoshis this separation aligns with lnurl best practices and adds stricter validation 5\ server js – lightning address server example below is a complete node js server example implementing the above logic this code uses rest calls (via axios) to lnd instead of grpc create a file server js and paste the following content require("dotenv") config(); const express = require("express"); const axios = require("axios"); const https = require("https"); const app = express(); const port = process env port || 3000; // load env variables const { lnd rest host, lnd rest macaroon hex, domain } = process env; console log("lnd rest host ", process env lnd rest host); // create an axios instance for lnd rest api calls const lightning = axios create({ baseurl lnd rest host, httpsagent new https agent({ rejectunauthorized false }), headers { "grpc metadata macaroon" lnd rest macaroon hex }, // macaroon auth header\ contentreference\[oaicite 12]{index=12} }); // basic home route (optional) app get("/", (req, res) => { res send("⚡ lightning address server is running!"); }); // lnurlp/ln address endpoint app get("/ well known/lnurlp/\ username", (req, res) => { const username = req params username; const metadata = \[ \["text/plain", `payment to ${username}@${domain}`], \["text/identifier", `${username}@${domain}`], ]; return res json({ tag "payrequest", callback `https //${domain}/lnurl/callback/${username}`, minsendable 1000, maxsendable 1000000000, metadata json stringify(metadata), }); }); app get("/lnurl/callback/\ username", async (req, res) => { const amountmsat = req query amount; if (!amountmsat) { return res status(400) json({ status "error", reason "missing 'amount' parameter" }); } try { const msat = parseint(amountmsat, 10); const satoshis = math floor(msat / 1000); const response = await lightning post("/v1/invoices", { value satoshis }); const paymentrequest = response data payment request; return res json({ pr paymentrequest, routes \[] }); } catch (err) { console error("invoice creation error ", err response? data || err); return res status(500) json({ status "error", reason "failed to create invoice" }); } }); // start the server app listen(port, () => { console log(`✅ lightning address server is listening on port ${port}`); }); let’s break down what this server does (updated) it sets up two clearly separated routes to comply with the lnurl pay protocol discovery endpoint the route at / well known/lnurlp/\ username responds with a payrequest object this includes a callback url (now pointing to a separate endpoint), minsendable, maxsendable, and metadata describing the payment target callback endpoint the route at /lnurl/callback/\ username handles the actual invoice creation it checks for the required amount parameter (in millisatoshis) if it’s missing, it returns an error as per lnurl spec if present, it uses axios to call lnd’s rest api and returns a pr (bolt11 invoice) and empty routes array axios is configured to talk to lnd’s rest api using the lnd rest host and macaroon from your env file the server listens on the specified port (3000 by default) tls verification is disabled for simplicity when using self signed certs (voltage’s rest interface can be trusted as it uses a proper ca) the use of env keeps your credentials secure and out of the codebase = environment tip keep your env values secure and never commit them to git the macaroon is like a password to your node (in this case, it grants invoice creation rights), so treat it confidentially running the server start the node server $ node server js ✅ lightning address server is listening on port 3000 your lightning address service is now live when a lightning wallet queries https //\<domain>/ well known/lnurlp/\<username>, it will receive the lnurl pay service response then, when it requests an invoice (by calling the callback url with an amount), your server will create an invoice via the voltage node and return it testing your lightning address (updated flow) wallet queries lnurl address https //\<domain>/ well known/lnurlp/\<username> receives metadata + callback url wallet sends amount to callback example https //\<domain>/lnurl/callback/\<username>?amount=10000 server generates invoice and responds with pr you can test with curl https //\<domain>/ well known/lnurlp/alice curl "https //\<domain>/lnurl/callback/alice?amount=10000" note on voltage, make sure your node’s ip whitelist includes the ip of your server (or is disabled) so that api calls from your server to the node are allowedvoltage cloud a 403 error in the axios request may indicate a missing whitelist entry for your server’s ip differences from the grpc approach migrating from grpc/ln service to rest+axios brings some changes worth noting no grpc dependency we removed the need for the grpc interface and ln service our example no longer loads tls certificate files or uses grpc libraries everything is done with http calls using axios, which can simplify deployment and avoid compatibility issues (especially on platforms where installing grpc is troublesome) environment variables instead of a socket address, cert, and macaroon file path, we now use a single lnd rest host url and a hex encoded macaroongithub com this can be easier to configure, especially with cloud nodes we no longer need to handle reading a cert file or configuring grpc channels tls handling voltage nodes already present a valid ca signed certificate, so axios’s default https validation works out of the box; no extra tls configuration is required streaming and listeners grpc allows streaming rpcs (for example, listening for invoices being paid) the rest api is request response only in this simple lightning address server, we don’t need streaming — we generate an invoice and that’s it however, if you wanted the server to wait for the invoice to be paid (and maybe confirm to the user), you’d need to poll the /v1/invoice/\<r hash> endpoint or use websocket/subscription solutions this is a bit less convenient than grpc’s streaming (e g , subscribeinvoices)lightning engineering in practice, lnurl pay flows usually don’t require the server to confirm payment in real time to the payer; the wallet takes care of notifying the user upon payment performance for most use cases, the rest approach performs similarly for creating invoices grpc might have a slight edge in high frequency scenarios or when sending large streams of data, but for on demand invoice creation the difference is negligible the trade off is well worth the simplicity for a small self hosted service code simplicity our node js code is straightforward and uses common libraries we avoided the intricacies of proto definitions and grpc clients this should make maintenance easier and the code more accessible to developers who aren’t familiar with grpc conclusion by refactoring the lightning address server to use voltage’s rest api, we achieved a simpler and more accessible setup while preserving full functionality we retained the original structure (initial lnurl fetch and invoice generation callback) but now use standard http calls to interface with lnd you can still receive payments directly into your own node, but with less overhead in your codebase to recap , you configured a voltage lnd node, set up a small express server with axios to create invoices, and served the lightning address protocol on your domain you can now share an address like alice\@yourdomain com and receive lightning payments straight into your node 🎉 feel free to extend this example you might add logging, support for multiple users, or even a simple frontend for instance, some implementations include a static webpage or redirect for /\<username> to show a donation page you could also integrate a database if you want to track payments or generate dynamic success messages the core logic, however, will remain the same – responding to lnurl requests and creating invoices finally, remember to keep your macaroon safe and never expose it publicly happy hacking, and enjoy your self hosted lightning address server! ⚡