Create invoices with a Node.JS cli using lnmessage and commando
In this video we write a Node.JS cli application that generates invoices by sending commando
messages to a CLN node using lnmessage
library.
Transcript with corrections and improvements
In this part, we write a nodejs cli app which creates invoices by
running the invoice
method on the node l2
(directly-connected peer)
using lnmessage library.
We can represent it using this diagram:
pays invoices created
by `invoice.js`
┌─────────┐ ┌─────────┐
│ node l1 │<--------------------->│ node l2 │
└─────────┘ └─────────┘
▲
│ - connects using `lnmessage`
│ - creates invoices via `commando`
┌──────────────┐
│ ./invoice.js │
└──────────────┘
Specifically, we want invoice.js
script to take two arguments, the
amount of the invoice in msat and that the description of the invoice,
such that we can create an invoice on the node l2
like this
◉ tony@tony:~/clnlive/:
$ ./invoice.js 10000 pizza
{..., bolt11: 'lnbcrt100n1...tll8phrt6j4atqqfv9p3u', ...}
and the node l1
can pay it like this:
◉ tony@tony:~/clnlive/:
$ l1-cli pay lnbcrt100n1...tll8phrt6j4atqqfv9p3u
...
Start 2 Lightning nodes running on regtest
Let's start two Lightning nodes running on the Bitcoin regtest
chain
by sourcing the script lightning/contrib/startup_regtest.sh
provided
in CLN repository and by running the command start_ln
:
◉ tony@tony:~/lnroom:
$ source lightning/contrib/startup_regtest.sh
...
◉ tony@tony:~/lnroom:
$ start_ln
...
We can check that l1-cli
is just an alias for lightning-cli
with the
base directory being /tmp/l1-regtest
:
◉ tony@tony:~/lnroom:
$ alias l1-cli
alias l1-cli='lightning-cli --lightning-dir=/tmp/l1-regtest'
Install lnmessage
Let's install lnmessage
:
◉ tony@tony:~/lnroom:
$ npm i lnmessage
Now in package.json
we set type
to module
:
{
"type": "module",
"dependencies": {
"lnmessage": "^0.2.6"
}
}
Connect to l2 with lnmessage
The beauty of lnmessage
and commando
plugin is that to get information
from our node or to tell our node to do stuff we need to connect to
the node. Once we are connected we use the capacity of nodes in the
Lightning Network to send each other messages to send commando
messages to our node with the right runes.
Let's see how we can connect ./invoice.js
script to l2
node using
lnmessage
.
We need l2
's node id, host and port. We get those informations by
running the following command:
◉ tony@tony:~/lnroom:
$ l2-cli getinfo | jq '.id, .binding'
"03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b"
[
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
]
To connect to l2
, in the invoice.js
file, we instantiate the ln
object with the class LnMessage
to which we provide the previous
information about the node l2
and also a tcp socket. Then, we call
ln.connect()
async method to connect to the node l2
:
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
const ln = new LnMessage({
remoteNodePublicKey: '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b',
tcpSocket: new net.Socket(),
ip: '127.0.0.1',
port: 7272
})
const connected = await ln.connect()
Before we run that script, let's open another terminal in which we
define the alias l2-cli
and check that the node l2
has no peers:
# TERMINAL 2
◉ tony@tony:~/lnroom:
$ alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/lnroom:
$ l2-cli listpeers
{
"peers": []
}
Now in the terminal 1, we run invoice.js
script
# TERMINAL 1
◉ tony@tony:~/lnroom:
$ ./invoice.js
and in the terminal 2 while ./invoice.js
is running we check that l2
has
a new peer (which is in fact ./invoice.js
):
# TERMINAL 2
◉ tony@tony:~/lnroom:
$ l2-cli listpeers
{
"peers": [
{
"id": "037b6daf05fa442584b4f6f47cc65d3740340c5a92944760364367f605756d16f2",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:60520"
],
"features": "0000000000000000"
}
]
}
Send a getinfo commando message to l2
Before we create invoices with ./invoice.js
, let's send commando
messages that ask the node l2
to run the getinfo
command.
To do that we need a rune
. Let's generate an unrestricted rune with
commando-rune
command:
◉ tony@tony:~/lnroom:
$ l2-cli commando-rune
{
"rune": "k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA==",
"unique_id": "0",
"warning_unrestricted_rune": "WARNING: This rune has no restrictions! Anyone who has access to this rune could drain funds from your node. Be careful when giving this to apps that you don't trust. Consider using the restrictions parameter to only allow access to specific rpc methods."
}
Now we can modify our script and call ln.commando()
method with the
field method
set to getinfo
and the field rune
set to the rune we got
above:
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
const ln = new LnMessage({
remoteNodePublicKey: '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b',
tcpSocket: new net.Socket(),
ip: '127.0.0.1',
port: 7272
})
const connected = await ln.connect()
if (connected) {
const getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: 'k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA=='
})
console.log(getinfo)
ln.disconnect()
}
Back to our terminal we can get information about our the node l2
by
running our script:
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
id: '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b',
alias: 'SLIMYTRAWL',
color: '03ab4b',
num_peers: 1,
num_pending_channels: 0,
num_active_channels: 0,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 1,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
Restrict the rune to only authorize the invoice method
As we are writing a command line application which only generates invoices, we don't want to use an unrestricted rune.
To generate a new rune restricted to the method invoice
from the
previous master rune, we run the following command:
◉ tony@tony:~/lnroom:
$ l1-cli -k commando-rune rune=k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA== \
► restrictions='[["method=invoice"]]'
{
"rune": "TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ==",
"unique_id": "0"
}
Now, we replace in invoice.js
the unrestricted run by this new rune:
if (connected) {
const getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
})
...
}
This leads us to the error Not authorized: method is not equal to
invoice
when we try to run our script
◉ tony@tony:~/lnroom:
$ ./invoice.js
node:internal/process/esm_loader:97
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
Node.js v18.17.0
which is normal because we send a commando
message that tries to run the
command getinfo
on the node l2
with a rune that allows only to run the
invoice
command.
Let's just modify our script to handle better those kind of error by
using a try...catch
block:
...
if (connected) {
try {
const getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
})
console.log(getinfo)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
Back to our terminal we check that the error is handled correctly:
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
Complete invoice.js to send invoice commando messages to l2
The invoice
method takes 3 arguments amount_msat
, label
(must be
unique) and description
.
We modify invoice.js
file to send commando
messages that run invoice
method on the node l2
:
...
if (connected) {
try {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: "10000",
label: "inv",
description: "pizza"
},
rune: 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
})
console.log(invoice)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
In the terminal, we see than we can generate an invoice by running that script:
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
payment_hash: '4ae1919ccf13022860328cba8df8de044291ef6aa865601ad9351f45180b77c7',
expires_at: 1692975366,
bolt11: 'lnbcrt100n1pjdlp5xsp5u8zy3yafvd3vr8zvhgv77ance3kmpzcfkztktkfrxw2dh33u3w2qpp5ftser8x0zvpzscpj3jagm7x7q3pfrmm24pjkqxkex5052xqtwlrsdqgwp5h57npxqyjw5qcqp29qxpqysgq9v20764zw5ndex5m99h3v3uprxwdwljx4apst937s39xr755zqzsqgnc6z65tlgr6yq55kw0ddw5eh7gvyyepwxrjlg2gr77sqg7d9gp2lpfc8',
payment_secret: 'e1c44893a96362c19c4cba19ef7678cc6db08b09b09765d9233394dbc63c8b94',
warning_capacity: 'Insufficient incoming channel capacity to pay invoice'
}
Let's use fund_nodes
command provided by the script
lightning/contrib/startup_regtest.sh
to fund a channel from the node
l1
to the node l2
:
◉ tony@tony:~/lnroom:
$ fund_nodes
Mining into address bcrt1q3g2uakt0tqdmn5aypr3k8cl5l3659ez2ue9wge... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.
Now the node l1
can pay the invoice we generated with invoice.js
script:
◉ tony@tony:~/lnroom:
$ l1-cli pay lnbcrt100n1pjdlp5xsp5u8zy3yafvd3vr8zvhgv77ance3kmpzcfkztktkfrxw2dh33u3w2qpp5ftser8x0zvpzscpj3jagm7x7q3pfrmm24pjkqxkex5052xqtwlrsdqgwp5h57npxqyjw5qcqp29qxpqysgq9v20764zw5ndex5m99h3v3uprxwdwljx4apst937s39xr755zqzsqgnc6z65tlgr6yq55kw0ddw5eh7gvyyepwxrjlg2gr77sqg7d9gp2lpfc8
{
"destination": "03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b",
"payment_hash": "4ae1919ccf13022860328cba8df8de044291ef6aa865601ad9351f45180b77c7",
"created_at": 1692370716.499,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "da22d040b5cd8456276c07a44f1557568c4e4eb84f902cbcc796881bea432988",
"status": "complete"
}
If we run again invoice.js
script we get the Duplicate label
error
because CLN requires that the label
field in the arguments of the
invoice
method to be unique (which is not our case thought we've hard
coded that value):
◉ tony@tony:~/lnroom:
$ ./invoice.js
{ code: 900, message: "Duplicate label 'inv'" }
Let's produce random labels for invoices using Math.random()
function
...
if (connected) {
try {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: "10000",
label: `inv-${Math.random()}`,
description: "pizza"
},
rune: 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
})
console.log(invoice)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
and check in our terminal that it works as expected:
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
payment_hash: 'a649975d22f292aaf583df80798a668a4671093930398b6b1cfb64d505e537b5',
expires_at: 1692975564,
bolt11: 'lnbcrt100n1pjdlp6vsp5gs2ma5s7e0v5cuqc5ywhlc66ngrsxt98cfedsu6xfckt8p4twcpqpp55eyewhfz72f24avrm7q8nznx3fr8zzfexquck6culdjd2p09x76sdqgwp5h57npxqyjw5qcqp29qxpqysgqhz07dvzcn4xyw8mh8nrt3zua52kduyxf0ynepfnvdz8puv6uz0j3crdunzpc06g3csa383ehqelh8pr2lvw8dr962n24ft4mquyfuxsqsjrsn8',
payment_secret: '4415bed21ecbd94c7018a11d7fe35a9a07032ca7c272d873464e2cb386ab7602',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
Last thing to do is to be able to pass the amount of the invoice and its description via command line arguments. This can be done like this:
...
if (connected) {
const [amountMsat, description] = process.argv.slice(2)
try {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: amountMsat,
label: `inv-${Math.random()}`,
description: description
},
rune: RUNE
})
console.log(invoice)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
We are now able to generate a invoice of 10000msat with the label
pizza
like this:
◉ tony@tony:~/lnroom:
$ ./invoice.js 10000 pizza
{
payment_hash: 'b365891fa4a97e249006d8c7f3105a4ef033ce6e0194d0510d2bbad6b2ecec7e',
expires_at: 1692975654,
bolt11: 'lnbcrt100n1pjdlpaxsp5ks409l0kwhzx8xxnhmgawrwzhpk6dy9ft22wmkdnup8wzhev6msqpp5kdjcj8ay49lzfyqxmrrlxyz6fmcr8nnwqx2dq5gd9waddvhva3lqdqgwp5h57npxqyjw5qcqp29qxpqysgqs7vganej2j72yxz9m8ch3dft274a78adxemvsj4c8ucy0a9jjwgk2h0w53mgemedfjv8aakj8jrlhswy9e3uhckhntq570mdvcdecrqq8llvq7',
payment_secret: 'b42af2fdf675c46398d3bed1d70dc2b86da690a95a94edd9b3e04ee15f2cd6e0',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
Finally we do a bit of refactoring and we get the following script
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
const NODE_ID = '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b'
const NODE_IP = '127.0.0.1'
const NODE_PORT = 7272
const RUNE = 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
const ln = new LnMessage({
remoteNodePublicKey: NODE_ID,
tcpSocket: new net.Socket(),
ip: NODE_IP,
port: NODE_PORT
})
const connected = await ln.connect()
if (connected) {
const [amountMsat, description] = process.argv.slice(2)
try {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: amountMsat,
label: `inv-${Math.random()}`,
description: description
},
rune: RUNE
})
console.log(invoice)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
which works perfectly:
◉ tony@tony:~/lnroom:
$ ./invoice.js 10000 pizza
{
payment_hash: 'a04de18dd66d7c63b5bbfdc3d99441059932e270feb421b5ce3da5e8c5f6c4b7',
expires_at: 1692975790,
bolt11: 'lnbcrt100n1pjdlzpwsp5uujpz46n5cp5q78k7zrxnj3e892sgslspuljl3kvzfpcxpme3z6qpp55px7rrwkd47x8ddmlhpan9zpqkvn9cnsl66zrdww8kj7330kcjmsdqgwp5h57npxqyjw5qcqp29qxpqysgqs9hz2zhfn5wqczmslhjg4mvvuwamd4dcea8fl52r4nyy54t4d4fzf0mkx2u8jxd0pf63gxl2gll49axy0vwgzezwym46uxcmd3t39pgqwph3ph',
payment_secret: 'e724115753a6034078f6f08669ca3939550443f00f3f2fc6cc124383077988b4',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
We are done!
Terminal session
We ran the following commands in this order:
$ ls
$ source lightning/contrib/startup_regtest.sh
$ start_ln
$ alias l2-cli
$ npm i lnmessage
$ l2-cli getinfo | jq '.id, .binding'
$ alias l2-cli
$ ./invoice.js
$ l2-cli commando-rune
$ ./invoice.js
$ l1-cli -k commando-rune rune=k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA== restrictions='[["method=invoice"]]'
$ ./invoice.js
$ ./invoice.js
$ ./invoice.js
$ fund_nodes
$ l1-cli pay lnbcrt100n1pjdlp5xsp5u8zy3yafvd3vr8zvhgv77ance3kmpzcfkztktkfrxw2dh33u3w2qpp5ftser8x0zvpzscpj3jagm7x7q3pfrmm24pjkqxkex5052xqtwlrsdqgwp5h57npxqyjw5qcqp29qxpqysgq9v20764zw5ndex5m99h3v3uprxwdwljx4apst937s39xr755zqzsqgnc6z65tlgr6yq55kw0ddw5eh7gvyyepwxrjlg2gr77sqg7d9gp2lpfc8
$ ./invoice.js
$ ./invoice.js
$ ./invoice.js 10000 pizza
$ ./invoice.js 10000 pizza
And below you can read the terminal session (command lines and outputs):
◉ tony@tony:~/lnroom:
$ ls
lightning/ invoice.js notes.org
◉ tony@tony:~/lnroom:
$ source lightning/contrib/startup_regtest.sh
lightning-cli is /usr/local/bin/lightning-cli
lightningd is /usr/local/bin/lightningd
Useful commands:
start_ln 3: start three nodes, l1, l2, l3
connect 1 2: connect l1 and l2
fund_nodes: connect all nodes with channels, in a row
stop_ln: shutdown
destroy_ln: remove ln directories
◉ tony@tony:~/lnroom:
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
error code: -35
error message:
Wallet "default" is already loaded.
[1] 12892
[2] 12925
WARNING: eatmydata not found: install it for faster testing
Commands:
l1-cli, l1-log,
l2-cli, l2-log,
bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/lnroom:
$ alias l2-cli
alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/lnroom:
$ npm i lnmessage
added 9 packages in 6s
5 packages are looking for funding
run `npm fund` for details
◉ tony@tony:~/lnroom:
$ l2-cli getinfo | jq '.id, .binding'
"03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b"
[
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
]
◉ tony@tony:~/lnroom:
$ alias l2-cli
alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/lnroom:
$ ./invoice.js
^C
◉ tony@tony:~/lnroom:
$ l2-cli commando-rune
{
"rune": "k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA==",
"unique_id": "0",
"warning_unrestricted_rune": "WARNING: This rune has no restrictions! Anyone who has access to this rune could drain funds from your node. Be careful when giving this to apps that you don't trust. Consider using the restrictions parameter to only allow access to specific rpc methods."
}
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
id: '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b',
alias: 'SLIMYTRAWL',
color: '03ab4b',
num_peers: 1,
num_pending_channels: 0,
num_active_channels: 0,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 1,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
◉ tony@tony:~/lnroom:
$ l1-cli -k commando-rune rune=k9KuYqPop1qtN4CmqFDVarwNrONhwc1MuIELzcUmmYw9MA== \
► restrictions='[["method=invoice"]]'
{
"rune": "TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ==",
"unique_id": "0"
}
◉ tony@tony:~/lnroom:
$ ./invoice.js
node:internal/process/esm_loader:97
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
Node.js v18.17.0
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
payment_hash: '4ae1919ccf13022860328cba8df8de044291ef6aa865601ad9351f45180b77c7',
expires_at: 1692975366,
bolt11: 'lnbcrt100n1pjdlp5xsp5u8zy3yafvd3vr8zvhgv77ance3kmpzcfkztktkfrxw2dh33u3w2qpp5ftser8x0zvpzscpj3jagm7x7q3pfrmm24pjkqxkex5052xqtwlrsdqgwp5h57npxqyjw5qcqp29qxpqysgq9v20764zw5ndex5m99h3v3uprxwdwljx4apst937s39xr755zqzsqgnc6z65tlgr6yq55kw0ddw5eh7gvyyepwxrjlg2gr77sqg7d9gp2lpfc8',
payment_secret: 'e1c44893a96362c19c4cba19ef7678cc6db08b09b09765d9233394dbc63c8b94',
warning_capacity: 'Insufficient incoming channel capacity to pay invoice'
}
◉ tony@tony:~/lnroom:
$ fund_nodes
Mining into address bcrt1q3g2uakt0tqdmn5aypr3k8cl5l3659ez2ue9wge... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.
◉ tony@tony:~/lnroom:
$ l1-cli pay lnbcrt100n1pjdlp5xsp5u8zy3yafvd3vr8zvhgv77ance3kmpzcfkztktkfrxw2dh33u3w2qpp5ftser8x0zvpzscpj3jagm7x7q3pfrmm24pjkqxkex5052xqtwlrsdqgwp5h57npxqyjw5qcqp29qxpqysgq9v20764zw5ndex5m99h3v3uprxwdwljx4apst937s39xr755zqzsqgnc6z65tlgr6yq55kw0ddw5eh7gvyyepwxrjlg2gr77sqg7d9gp2lpfc8
{
"destination": "03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b",
"payment_hash": "4ae1919ccf13022860328cba8df8de044291ef6aa865601ad9351f45180b77c7",
"created_at": 1692370716.499,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "da22d040b5cd8456276c07a44f1557568c4e4eb84f902cbcc796881bea432988",
"status": "complete"
}
◉ tony@tony:~/lnroom:
$ ./invoice.js
{ code: 900, message: "Duplicate label 'inv'" }
◉ tony@tony:~/lnroom:
$ ./invoice.js
{
payment_hash: 'a649975d22f292aaf583df80798a668a4671093930398b6b1cfb64d505e537b5',
expires_at: 1692975564,
bolt11: 'lnbcrt100n1pjdlp6vsp5gs2ma5s7e0v5cuqc5ywhlc66ngrsxt98cfedsu6xfckt8p4twcpqpp55eyewhfz72f24avrm7q8nznx3fr8zzfexquck6culdjd2p09x76sdqgwp5h57npxqyjw5qcqp29qxpqysgqhz07dvzcn4xyw8mh8nrt3zua52kduyxf0ynepfnvdz8puv6uz0j3crdunzpc06g3csa383ehqelh8pr2lvw8dr962n24ft4mquyfuxsqsjrsn8',
payment_secret: '4415bed21ecbd94c7018a11d7fe35a9a07032ca7c272d873464e2cb386ab7602',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/lnroom:
$ ./invoice.js 10000 pizza
{
payment_hash: 'b365891fa4a97e249006d8c7f3105a4ef033ce6e0194d0510d2bbad6b2ecec7e',
expires_at: 1692975654,
bolt11: 'lnbcrt100n1pjdlpaxsp5ks409l0kwhzx8xxnhmgawrwzhpk6dy9ft22wmkdnup8wzhev6msqpp5kdjcj8ay49lzfyqxmrrlxyz6fmcr8nnwqx2dq5gd9waddvhva3lqdqgwp5h57npxqyjw5qcqp29qxpqysgqs7vganej2j72yxz9m8ch3dft274a78adxemvsj4c8ucy0a9jjwgk2h0w53mgemedfjv8aakj8jrlhswy9e3uhckhntq570mdvcdecrqq8llvq7',
payment_secret: 'b42af2fdf675c46398d3bed1d70dc2b86da690a95a94edd9b3e04ee15f2cd6e0',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/lnroom:
$ ./invoice.js 10000 pizza
{
payment_hash: 'a04de18dd66d7c63b5bbfdc3d99441059932e270feb421b5ce3da5e8c5f6c4b7',
expires_at: 1692975790,
bolt11: 'lnbcrt100n1pjdlzpwsp5uujpz46n5cp5q78k7zrxnj3e892sgslspuljl3kvzfpcxpme3z6qpp55px7rrwkd47x8ddmlhpan9zpqkvn9cnsl66zrdww8kj7330kcjmsdqgwp5h57npxqyjw5qcqp29qxpqysgqs9hz2zhfn5wqczmslhjg4mvvuwamd4dcea8fl52r4nyy54t4d4fzf0mkx2u8jxd0pf63gxl2gll49axy0vwgzezwym46uxcmd3t39pgqwph3ph',
payment_secret: 'e724115753a6034078f6f08669ca3939550443f00f3f2fc6cc124383077988b4',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
# TERMINAL 2
◉ tony@tony:~/lnroom:
$ alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/lnroom:
$ l2-cli listpeers
{
"peers": []
}
◉ tony@tony:~/lnroom:
$ l2-cli listpeers
{
"peers": [
{
"id": "037b6daf05fa442584b4f6f47cc65d3740340c5a92944760364367f605756d16f2",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:60520"
],
"features": "0000000000000000"
}
]
}
Source code
invoice.js
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
const NODE_ID = '03ab4b3226cc22c3ebc4cdaecf4ecc0ca8e2238d4392b4d900f4546328cb616e6b'
const NODE_IP = '127.0.0.1'
const NODE_PORT = 7272
const RUNE = 'TUNWsckQn6U5p4Tz3mkmWve0OqtBPlAQ3MnyXr3jvZE9MCZtZXRob2Q9aW52b2ljZQ=='
const ln = new LnMessage({
remoteNodePublicKey: NODE_ID,
tcpSocket: new net.Socket(),
ip: NODE_IP,
port: NODE_PORT
})
const connected = await ln.connect()
if (connected) {
const [amountMsat, description] = process.argv.slice(2)
try {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: amountMsat,
label: `inv-${Math.random()}`,
description: description
},
rune: RUNE
})
console.log(invoice)
} catch (err) {
console.error(err)
}
ln.disconnect()
}
package.json
{
"type": "module",
"dependencies": {
"lnmessage": "^0.2.6"
}
}