Back to the Blog

Send and receive Bitcoin over the Lightning Network with NodeJS

News

Introduction

In this article, you will write a NodeJS script to interact with a Lightning node to send and receive Bitcoin programmatically.

If you don’t already have a node up and running, Voltage’s hosted node offering lets you spin up a node in just a few minutes without the hassle of dealing with infrastructure yourself.

Prerequisites:

  • A mainnet LND lightning node with at least one funded channel
  • A lightning wallet
  • A development environment with NodeJS installed

Step 1 – Testing the Lightning Node

First, do a sanity check to see that your lightning node can generate and pay invoices. Note that the screenshots in this article come from the Thunderhub and Muun Wallet GUIs, but feel free to use a dashboard and wallet of your choice.

Let’s generate a 5,000 sat invoice from our node and pay it with our wallet:

Create an invoice with your wallet and pay it with your node. Once you’re satisfied that payments are succeeding consistently, continue to the next step to connect to your node.

Step 2 – Connecting to your node

Now that you know that your node is working as expected, set up a node project with npm:

$ mkdir lightning-example
$ cd lightning-example 
$ npm init -y
$ npm i axios

In order to connect to your node, you will need its URL and its admin.macaroon. The URL is where you will point your requests to and the admin.macaroon will be passed in the header of the requests to authenticate the requests. If you’re using Voltage, both are found in the “Connect” tab in the Voltage dashboard.

Save the admin.macroon in the lightning-example directory, create a file called index.js and paste the following to read the macaroon:

const fs = require('fs')

const macaroon = fs.readFileSync('./admin.macaroon').toString('hex')

To save having to duplicating the node’s URL each request, create an axios instance that can be reused for multiple requests:

const axios = require('axios')
const https = require('https')

const myNode = axios.create({
  baseURL: 'https://<your_node_URL>.m.voltageapp.io:8080',
  httpsAgent: new https.Agent({
    rejectUnauthorized: false
  }),
  headers: { 'Grpc-Metadata-macaroon': macaroon }
})

Now write the request to call the getinfo endpoint:

const getInfo = async () => {
  try {
    let res = await myNode.post('/v1/getinfo')
    return res
  } catch(err) {
    console.error(err.repsonse)
  }
}

const run = async () => {
  const res = await getInfo()
  console.log(res.data)
}

run()

Running it from the command line should give the result:

$ node index.js
{
  version: '0.13.4-beta commit=v0.13.4-beta.f1'....
}

Great, now you have established a connection to your node! If you’re getting a 403 error, check that your IP is whitelisted (this is found under Voltage’s “Settings” panel).

const payInvoice = async (invoice) => {
  try {
    let res = await myNode.post('/v2/router/send', {
      payment_request: invoice, 
      timeout_seconds: 60, 
      fee_limit_sat: 100 
    })
    return res
  } catch (err) {
    console.error(err.response)
  }
}

Note that the timeout and fee limit have to be set otherwise they will default to 0 which will result in failed payments. It’s also important to note that the terms payment request and invoice are interchangeable terms but LND’s REST API refers to them as payment requests.

Adjust the run function to read the invoice from the command line:

const run = async () => {
  const invoice = process.argv[2]
  const res = await payInvoice(invoice)
  console.log(res.data)
}

run()

Create an invoice from your wallet and paste it into your terminal:

$ node index.js lnbc24900n1p39jev5pp53fd3yg3aqzrvh4c6zjy97pzmztx2tcz00xs5...

A successful payment should give the following result:

{"result":{"payment_hash":"8a5b12223d0086cbd71a148..."}

Keep your wallet app open and it should inform you of the payment being received:

You’ve now paid an invoice from your node!

Step 4 – Generating invoices and checking for settlement

The last step will be to generate invoices and check that they are settled.

Creating invoices is fairly straightforward:

const createInvoice = async (amountSatoshis) => {
  try {
    let res = await myNode.post('/v1/invoices', {
      value: amountSatoshis
    })
    return res
  } catch (err) {
  console.error(err.response)
  }
}

Update the run() function:

const run = async () => {
  const res = await createInvoice(5000)
  console.log(res.data)
}

run()

We now have an invoice that should be payable with your wallet:

$ node server.js
{
  r_hash: 'p89HSZZXUuviPvZTjYvFhFtCq5KoIJv/KyOqCoT7c7k=',
  payment_request: 'lnbc50u1p39j78qpp55l85wjvk2afwhc377efcmz79s3d592uj4qsfhletyw4q4p8mwwusdqqcqzpgxqyz5vqsp5qpdl5yncsjy63qmnmyf00sfcynfwf6xdjl0zapgk7awh8v58de0s9qyyssqfeg9yqu8t3xl0nk3yctntrwrtg3t46n7smf3amz40vsa3mpw8faqg3mqnkdgu9fglskpgp38yk3m9d5usc0wss7eptt6xls5cpf4q9sqfydmjw',
  add_index: '210',
  payment_addr: 'AFv6EniEiaiDc9kS98E4JNLk6M2X3i6FFvddc7KHbl8='
}

Note that the r_hash will be used to check the invoice status later.

Paste the payment_request in your wallet and pay it:

When developing an application, it’s important for the application to check on its own end that the invoice has been settled. This can be done by calling the following invoice endpoint and parsing its status:

const checkSettlement = async (invoice) => {
  try {
    const buffer = Buffer.from(invoice, 'base64')
    let res = await myNode.get(`/v1/invoice/${buffer.toString('hex')}`)
    return res
  } catch (err) {
    console.log(err.response)
  }

Finally, update the run function:

const run = async () => {
  const invoice = process.argv[2]
  const res = await checkSettlement(invoice)
  console.log(res.data.settled)
}

run()

Now paste in the r_hash associated with the paid invoice:

$ node index.js p89HSZZXUuviPvZTjYvFhFtCq5KoIJv/KyOqCoT7c7k
true

As expected, the invoice is settled!

Conclusion

In this tutorial you have created a NodeJS script to interact with your lightning node. You were able to check for the node’s status, send and receive payments and check the settlement status of the payments.

These functions cover the most fundamental use cases when dealing with lightning be used as the building blocks for creating a lightning based service.

Resources