If you’ve been following Bitcoin, you’ve probably heard of the Lightning network: a layer 2 solution to the on-chain scaling problems that Bitcoin faces that allows for near-instantaneous payments with extremely low fees.
The Lightning network has caught fire over the last year, with over 8,000 nodes online and over 1,000 BTC in public channels. We’ve also seen a ton of cool applications pop up that take advantage of Lightning’s micropayments.
Whether it’s paying for the latest Y’alls post, making pixel-per-sat doodles on Satoshis.place, or taking a chance to win big on Lightning Spin, these have all been exciting examples of what we can do with the Lightning network.
But even so, it hasn’t been easy to get started as a developer to get started with making a Lightning-powered web app of your own. So that’s why I’m putting together this 5-part series on how to make a Lightning app, using the latest industry-standard tech.
Let’s take an overview of what we’ll be using:
If you’re a more advanced user and aren’t afraid of a little setup, you can run a local simulated network to be able to test multi-user behavior without having to use real bitcoin or wait for block confirmations.
To build our Lightning app, we’ll be using the following technology:
If you’re not familiar with React or Express, I suggest taking just a little bit of time to brush up on them before moving on, as we won’t be covering the basics. But it shouldn’t be difficult to follow along, even if you’re fairly new.
Getting Started: Clone Our Project
To start off, we’ll clone or download a starting point for our project that you can find here https://github.com/wbobeirne/lightning-app-tutorial/tree/part-1:
git clone https://github.com/wbobeirne/lightning-app-tutorial.git
git checkout part-1
The files we’ll be focusing on are:
- .env.example — A file that shows an example of how to format our
.envfile that contains all of our configurations and secret variables we don’t want checked into source code.
- server/*.ts — The code that runs our Express webserver lives in here. We’ll be going over each file below.
First things first, we’ll need to install the dependencies our project needs to run. Do this by running:
That will install everything listed in
package.json. After that, we’ll want to copy the
.env.example file over to a new file called
.env. Make sure you copy that correctly!
cp .env.example .env
Let’s open our new
.env file, it should look something like this:
We need to configure these variables for our local setup, since everyone’s is different. These won’t get checked into source code, so don’t worry about leaking any sensitive data.
To fill these out, we’ll need to get to our node’s data directory and figure out its connection info. To get the default port of your node and the path to the, you can check out this handy tool which will tell you based on your OS and node type: https://lightningjoule.com/tools/node-info.
PORT— This is the port your app is served on, NOT the port of your node. This can be left as-is, but feel free to change it.
LND_GRPC_URL— If you’re running your node locally with default settings, this should be
127.0.0.1:[port]. If it’s remote, you’ll need to use the IP of the remote device. You can find the port in the tool above.
LND_MACAROON— Credentials to access your node. Macaroons are files that give limited permissioned access to external applications. They can be found in the data directory in
mainnetif you’re running a different kind of node.)
We’ll be using your
readonly.macaroonwhich will limit us to only run informational commands from your node. The app will be unable to spend money or do anything dangerous with it. We will need to change this in the future as our app needs to do more things with our node. Run
base64 readonly.macaroonin your macaroon directory to get the string.
LND_TLS_CERT— Same idea as above, but this comes from the
tls.certfile in the base data directory. This allows us to encrypt our communication with the node. Run
base64 tls.certin your data directory to get the string.
Once you’ve filled in all of that information in your
.env file, you're good to go! If you don’t have access to the
base64 command, you can use an online tool.
Now that your environment has been configured, it’s time to run the server. But before we do that, let’s take a look at each file and what it does:
This loads all of our environment variables from
.env into an object that we can use throughout our code. It also checks to ensure we've set all of the variables in the
.env file. However, it won’t detect misconfigurations such as an invalid macaroon, TLS cert, or gRPC URL.
This file contains all the setup we need to connect to our node. After running
initNode, we can import the node object and make RPC calls to our heart's content.
We’ll cover this in two parts, but it’s all one file:
This code starts up our server, but initializes our node gRPC interface first. This ensures none of our endpoints will get hit until we’ve gotten that all setup. You might end up adding more setup here for things like a database, Redis store, or any other asynchronous initializers.
Now that we have our server, let’s look at our first & only route:
We have only one endpoint at the root path of
/ that returns some info about your node. All node requests are asynchronous since it needs to talk to your node over the network, so we have to
await calls to the node, and handle any errors that might come back.
And that’s it for code in our initial project! If all goes according to plan, you should be able to run the following command:
npm run dev
And go to http://localhost:3000/ to see the following:
If the above didn’t work for you, you may be experiencing one of these errors:
- “Required environment variable… is missing!” — Your
.envfile was not properly configured. Either you never created it, it was copied in the wrong place, has the wrong name, or some of the variables are missing.
- “UNAVAILABLE: Connect Failed” — Your node could not be reached at the address you specified. If it’s a local node, check the logs of your LND node to see if there are any errors, and double check the port to make sure it’s correct. If it’s a remote node, check that you’ve opened up any firewalls or forwarded the necessary port. This could also mean your tls.cert is invalid.
- “UNKNOWN: permission denied” — This means the macaroon you provided was invalid. Make sure you grabbed the correct one for the node you’re connecting to.
Getting a different error? Please post an issue on the GitHub repository for the project, and I’ll be sure to address it as soon as possible.
Stick around for the next parts of this tutorial, where we’ll put together a slick React + Bootstrap frontend to start letting users pay to post content, get real-time updates from the server with websockets, integrate WebLN, and finally learn how to deploy a secure, robust production version that you can share around!