Introduction to commando and commando-rune
In this live we see how to create runes (restrictions tokens) with commando-rune
command and how to use them with commando
command in order to run authorized (by the rune) methods on a directly-connected peer. Then we write a nodejs cli app which runs the getinfo
method on a directly-connected peer using lnmessage
library. Finally, we write a nodejs cli app which creates invoices by running the invoice
method on a directly-connected peer using lnmessage
library. This gives use the opportunity to see how to add restrictions to runes.
Transcript with corrections and improvements
The commando RPC command lets us send messages to a directly-connected peer containing a request to run on the peer node.
The peer will allow us to run the method if it has provided us with a rune which allows it.
The peer uses the commando-rune RPC command to create such a rune (which is a base64 string).
Each rune contains a unique id (a number starting at 0), and can have restrictions inside it. Nobody can remove restrictions from a rune.
See rustyrussell/runes for more details.
Deprecated APIs (in the future maybe)?
Runes migration from commando to lightning #6403
[new]:
checkrune
: check rune validity for authorization[rename]:
commando-rune
->createrune
commando-listrunes
->listrunes
commando-blacklist
->blacklistrune
l1 and l2
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:~/clnlive:
$ source lightning/contrib/startup_regtest.sh
...
◉ tony@tony:~/clnlive:
$ 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:~/clnlive:
$ alias l1-cli
alias l1-cli='lightning-cli --lightning-dir=/tmp/l1-regtest'
Connect l1 with l2
To let the node l2
use commando
in order to run for instance the
command getinfo
on the node l1
, both node l1
and l2
needs to be
connected. We connect them using connect
command provided by
lightning/contrib/startup_regtest.sh
script
◉ tony@tony:~/clnlive:
$ connect 1 2
{
"id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"features": "08a0000a0269a2",
"direction": "out",
"address": {
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
}
and we list the peers of l1
node like this:
◉ tony@tony:~/clnlive:
$ l1-cli listpeers
{
"peers": [
{
"id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:7272"
],
"features": "08a0000a0269a2"
}
]
}
l1 creates an unrestricted rune
The node l1
creates an unrestricted rune using commando-rune
like this
(We can think of a rune as a token with restrictions that the node l1
can give to some application to get access to some commands of l1
's
node):
◉ tony@tony:~/clnlive:
$ l1-cli commando-rune
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"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."
}
Note that as this rune has no restriction, if the node l1
gives it to
a non trusted application, that application can do anything with the
node, specifically, it can use the command withdraw
to steal all the
onchain funds of l1
's node.
We can decode that rune using decode
command:
◉ tony@tony:~/clnlive:
$ l1-cli -k decode string=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"type": "rune",
"unique_id": "0",
"string": "71355f48e7f7e28a066ba8005723a8ec697d48c4dac05a0e7f4c0f44df9b84c5:=0",
"restrictions": [],
"valid": true
}
l2 calls commando to getinfo on l1
As we are going to use commando
command, let's write its signature
first:
commando peer_id method [params] [rune]
Now, with l1
's rune and its id
◉ tony@tony:~/clnlive:
$ l1-cli getinfo | jq -r .id
02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df
the node l2
can call the command commando
in order to run the
getinfo
method on the node l1
like this:
◉ tony@tony:~/clnlive:
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df \
► getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"alias": "SLEEPYPHOTO",
"color": "02139b",
"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": 7171
}
],
"version": "v23.05.2",
"blockheight": 1,
"network": "regtest",
"fees_collected_msat": 0,
"lightning-dir": "/tmp/l1-regtest/regtest",
"our_features": {
"init": "08a0000a0269a2",
"node": "88a0000a0269a2",
"channel": "",
"invoice": "02000002024100"
}
}
l2 withdraws all l1's onchain funds
Let's use the command fund_nodes
from
lightning/contrib/startup_regtest.sh
to fund l1
wallet and a channel
from l1
to l2
:
◉ tony@tony:~/clnlive:
$ fund_nodes
Mining into address bcrt1qefqgq474z47e0mx8kpcwhltwhsr3n0nesg49c9... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.
We can check the funds of the node l1
like this:
◉ tony@tony:~/clnlive:
$ l1-cli listfunds
{
"outputs": [
{
"txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"output": 0,
"amount_msat": 98999846000,
"scriptpubkey": "00147d3dc7584ba33b8bf92c11668357e29977d99f21",
"address": "bcrt1q057uwkzt5vach7fvz9ngx4lzn9man8epp8e5jc",
"status": "confirmed",
"blockheight": 103,
"reserved": false
}
],
"channels": [
{
"peer_id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"state": "CHANNELD_NORMAL",
"channel_id": "59e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1d",
"short_channel_id": "103x1x1",
"our_amount_msat": 1000000000,
"amount_msat": 1000000000,
"funding_txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"funding_output": 1
}
]
}
Now, let's see how l2
can withdraw all l1
's onchain funds using
commando
command, withdraw
command and the unrestricted rune
previously generated by l1
.
First we need an address belonging to l2
's wallet:
◉ tony@tony:~/clnlive:
$ l2-cli newaddr
{
"bech32": "bcrt1qcsk9r09shnmwaeqxckwvgklkmsuwrmvt8r6euu"
}
Then we can run the following command which withdraw all the onchain
fund of l1
's node:
◉ tony@tony:~/clnlive:
$ l2-cli -k commando peer_id=02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df \
► method=withdraw \
► params='{"destination": "bcrt1qcsk9r09shnmwaeqxckwvgklkmsuwrmvt8r6euu", "satoshi": "all"}' \
► rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"tx": "020000000159e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1c0000000000fdffffff01b89de60500000000160014c42c51bcb0bcf6eee406c59cc45bf6dc38e1ed8b6c000000",
"txid": "f5493851d1ee0bfe410d426c45850d26a41411dd524ee430389451d7f4b5b274",
"psbt": "cHNidP8BAgQCAAAAAQMEbAAAAAEEAQEBBQEBAQYBAwH7BAIAAAAAAQDqAgAAAAABAZDi4Lh3qxCW2XZnhXIIdz1BF3lbYBtKlrklY2rTtfGfAAAAAAD9////Aiae5gUAAAAAFgAUfT3HWEujO4v5LBFmg1fimXfZnyFAQg8AAAAAACIAIE63d7Xc+sw1idoVUOMvIkeLz5SoRieGPZRwMnI0CJ2+AkcwRAIgWJvJVdV12C4Y2FusM7BVIfSSlJKR+ZKJdZ25GbynIfYCIBPApKs0nAENTd0H1p+5x9jtdkmnORx9rlaRoOlZGXiEASECBLOpXSa0gdYoygpFI2WqWorsuN6S1tpkorMHhv/mymdmAAAAAQEfJp7mBQAAAAAWABR9PcdYS6M7i/ksEWaDV+KZd9mfISICAkTdt1WBadiveioGL6SQAXheTDEWPYHoufQVkVtw4QxQRzBEAiBj+AcJ5+mmb0bnRmfmMzr9a9ecjq6IGRuI+SHUT4yongIgRVLsJ31TajY/mbWQxoZA+TDOl+htW4lsEt9P2bAVHDUBIgYCRN23VYFp2K96KgYvpJABeF5MMRY9gei59BWRW3DhDFAIfT3HWAAAAAABDiBZ4qiY/tavdeh5b1Ta79u76QcHH1Dog72UG7gDEONeHAEPBAAAAAABEAT9////AAEDCLid5gUAAAAAAQQWABTELFG8sLz27uQGxZzEW/bcOOHtiwz8CWxpZ2h0bmluZwQCAAEA"
}
As we are running the nodes on regtest, we need to mine a block to get that last transaction including in a block. We can do this like this:
◉ tony@tony:~/clnlive:
$ bt-cli -rpcwallet=default getnewaddress
bcrt1q8k3lut8j9nyh8wp7afwqzf88g6sdxtpdq5h0rc
◉ tony@tony:~/clnlive:
$ bt-cli generatetoaddress 1 bcrt1q8k3lut8j9nyh8wp7afwqzf88g6sdxtpdq5h0rc
[
"0c06ae63b41ba69c5a5885424d4cc1e0c351ecdae1fdf71c968b0b0dd8f4c048"
]
Note that bt-cli
is an alias for bitcoin-cli -regtest
.
After l1
has retrieved the informations from bitcoind
, we can check
that l1
's wallet is now empty:
◉ tony@tony:~/clnlive:
$ l1-cli listfunds
{
"outputs": [],
"channels": [
{
"peer_id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"state": "CHANNELD_NORMAL",
"channel_id": "59e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1d",
"short_channel_id": "103x1x1",
"our_amount_msat": 1000000000,
"amount_msat": 1000000000,
"funding_txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"funding_output": 1
}
]
}
Add restriction readonly to l1's unrestricted rune
Each new master rune is saved and can be listed with
commando-listrunes
command like this:
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
}
]
}
We can add restrictions to runes using commando-rune
command.
Let add the specific restriction readonly
to the l1
unrestricted rune
cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
:
◉ tony@tony:~/clnlive:
$ l1-cli -k commando-rune rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA== \
► restrictions=readonly
{
"rune": "k6YhINmpUP99wRYmGA2x_gJjcDFhLDIzO3uU9bPu20E9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "0"
}
Note that this rune got the same unique id has its master rune.
By decoding that rune, we can see that this new rune authorized the methods:
starting by
list
likelistpeers
(method^list
) but notlistdatastore
(method/listdatastore
),starting by
get
likegetinfo
(method^get
),and
summary
(method=summary
):
◉ tony@tony:~/clnlive:
$ l1-cli decode k6YhINmpUP99wRYmGA2x_gJjcDFhLDIzO3uU9bPu20E9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl
{
"type": "rune",
"unique_id": "0",
"string": "93a62120d9a950ff7dc11626180db1fe02637031612c32333b7b94f5b3eedb41:=0&method^list|method^get|method=summary&method/listdatastore",
"restrictions": [
{
"alternatives": [
"method^list",
"method^get",
"method=summary"
],
"summary": "method (of command) starts with 'list' OR method (of command) starts with 'get' OR method (of command) equal to 'summary'"
},
{
"alternatives": [
"method/listdatastore"
],
"summary": "method (of command) unequal to 'listdatastore'"
}
],
"valid": true
}
See lightning:doc/lightning-commando-rune.7.md for more information about the restriction format.
We don't do it, but if we fund again l1
wallet and try to withdraw all
l1
's onchain funds using l2
node and commando
like we did before but
with this readonly
rune, it won't work.
l1 creates a new readonly rune
The restrictions to the runes can also be put at the creation of the master rune.
For instance, l1
can creates a new master rune with the readonly
restriction like this:
◉ tony@tony:~/clnlive:
$ l1-cli commando-rune null readonly
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1"
}
So far l1
node issued two master runes that we can list like this:
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
},
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1",
"restrictions": [
{
"alternatives": [
{
"fieldname": "method",
"value": "list",
"condition": "^",
"english": "method starts with list"
},
{
"fieldname": "method",
"value": "get",
"condition": "^",
"english": "method starts with get"
},
{
"fieldname": "method",
"value": "summary",
"condition": "=",
"english": "method equal to summary"
}
],
"english": "method starts with list OR method starts with get OR method equal to summary"
},
{
"alternatives": [
{
"fieldname": "method",
"value": "listdatastore",
"condition": "/",
"english": "method unequal to listdatastore"
}
],
"english": "method unequal to listdatastore"
}
],
"restrictions_as_english": "method starts with list OR method starts with get OR method equal to summary AND method unequal to listdatastore"
}
]
}
l1 blacklists the unrestricted rune
At some point, l1
may want to revoke some runes.
This is possible with the command commando-blacklist
.
To do so l1
needs to use the unique id of the rune.
For instance, to revoke the unrestricted rune
cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
which got the unique
id 0
, l1
runs the following command:
◉ tony@tony:~/clnlive:
$ l1-cli commando-blacklist 0
{
"blacklist": [
{
"start": 0,
"end": 0
}
]
}
Note that any rune that is a descendant of the master rune with unique
id 0
will also be revokated.
We can check in the list of the runes that the rune with unique id 0
has now the field blacklisted
set to true
:
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"blacklisted": true,
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
},
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1",
"restrictions": [
{
"alternatives": [
{
"fieldname": "method",
"value": "list",
"condition": "^",
"english": "method starts with list"
},
{
"fieldname": "method",
"value": "get",
"condition": "^",
"english": "method starts with get"
},
{
"fieldname": "method",
"value": "summary",
"condition": "=",
"english": "method equal to summary"
}
],
"english": "method starts with list OR method starts with get OR method equal to summary"
},
{
"alternatives": [
{
"fieldname": "method",
"value": "listdatastore",
"condition": "/",
"english": "method unequal to listdatastore"
}
],
"english": "method unequal to listdatastore"
}
],
"restrictions_as_english": "method starts with list OR method starts with get OR method equal to summary AND method unequal to listdatastore"
}
]
}
If the node l2
try to call commando
with that blacklisted rune as
before, this is the error message it gets:
◉ tony@tony:~/clnlive:
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df \
► getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"code": 19537,
"message": "Not authorized: Blacklisted rune"
}
Run getinfo method on the node l2 using lnmessage - getinfo.js
In this second part we write a nodejs cli app which runs the getinfo
method on the node l2
(directly-connected peer) using lnmessage
library.
The library lnmessage
is able to connect to CLN nodes and to send
commando
requests to those nodes.
We use it to connect to the node l2
. To do so we need the following
informations about l2
◉ tony@tony:~/clnlive:
$ l2-cli getinfo | jq '.id, .binding'
"0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7"
[
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
]
that we use to set the top variables NODE_ID
, NODE_IP
and NODE_PORT
in
getinfo.js
file. Those variables are used to instantiate ln
object
with the class LnMessage
. Finally, we can connect to the node l2
with
ln.connect
async method:
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
// node l2
const NODE_ID = '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7'
const NODE_IP = '127.0.0.1'
const NODE_PORT = '7272'
const ln = new LnMessage({
remoteNodePublicKey: NODE_ID,
tcpSocket: new net.Socket(),
ip: NODE_IP,
port: NODE_PORT
})
await ln.connect()
Before we run that script, we install lnmessage
◉ tony@tony:~/clnlive:
$ npm i lnmessage
and add "type": "module"
in package.json
file:
{
"type": "module",
"dependencies": {
"lnmessage": "^0.2.2"
}
}
We also open another terminal where we define the alias l2-cli
of the
node l2
and we check that l2
has only one peer so far (being the node
l1
):
# TERMINAL 2
◉ tony@tony:~/clnlive:
$ alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive:
$ l2-cli listpeers
{
"peers": [
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"connected": true,
"num_channels": 1,
"netaddr": [
"127.0.0.1:50508"
],
"features": "08a0000a0269a2"
}
]
}
Now in the terminal 1, we can run getinfo.js
script and see that it
hangs:
◉ tony@tony:~/clnlive:
$ ./getinfo.js
^C
This is normal because it is now connected to the node l2
and it is
waiting to do something. Indeed, in the terminal 2 we can check that
l2
has two peers one being getinfo.js
:
# TERMINAL 2
◉ tony@tony:~/clnlive:
$ l2-cli listpeers
{
"peers": [
{
"id": "0390d494b9b1f2b396ad79f7069b5230b6a7247c565926d9e6b077739d583c0855",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:48452"
],
"features": "0000000000000000"
},
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"connected": true,
"num_channels": 1,
"netaddr": [
"127.0.0.1:50508"
],
"features": "08a0000a0269a2"
}
]
}
Now to use commando
with lnmessage
and run getinfo
on the node l2
, we
need a rune created by l2
that allows getinfo
method.
Let's create such a rune:
◉ tony@tony:~/clnlive:
$ l2-cli commando-rune null readonly
{
"rune": "9xepUI9PBgd8dJhPUoPnelh2GPRL1TSD3-nf_jdGrzo9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "0"
}
Note that readonly
runes allow more than the getinfo
method.
Now we set the top variable RUNE
with that rune. Then, after we
connect to the node l2
we use ln.commando
async method to run getinfo
on the node l2
using that rune. Once done we print the information
about l2
. Finally we disconnect ourself and l2
with ln.disconnect
method:
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
// node l2
const NODE_ID = '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7'
const NODE_IP = '127.0.0.1'
const NODE_PORT = '7272'
// rune
const RUNE = '9xepUI9PBgd8dJhPUoPnelh2GPRL1TSD3-nf_jdGrzo9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl'
const ln = new LnMessage({
remoteNodePublicKey: NODE_ID,
tcpSocket: new net.Socket(),
ip: NODE_IP,
port: NODE_PORT
})
await ln.connect()
const getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: RUNE
})
console.log(getinfo)
ln.disconnect()
Back to our terminal, we can run getinfo.js
script and get the
information about the node l2
:
◉ tony@tony:~/clnlive:
$ ./getinfo.js
{
id: '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7',
alias: 'VIOLETBOUNCE',
color: '0365a1',
num_peers: 2,
num_pending_channels: 0,
num_active_channels: 1,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 109,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
Really cool!
Let's continue.
Creates invoices using lnmessage - invoice.js
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
...
Writing invoice.js
script gives the opportunity to see how to add
restrictions to a master rune.
Specifically, we will create a master rune that allows only the
invoice
method and then we will restrict that rune such that it allows
only 5 invoice
request per minute.
So with that last rune, we will be able to create only 5
invoices per minutes using invoice.js
.
Let's copy getinfo.js
file into the file invoice.js
wrapping the
commando
call into an if
statement and keeping the readonly rune
created previously by l2
:
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
// node l2
const NODE_ID = '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7'
const NODE_IP = '127.0.0.1'
const NODE_PORT = '7272'
// rune
const RUNE = '9xepUI9PBgd8dJhPUoPnelh2GPRL1TSD3-nf_jdGrzo9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl'
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 getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: RUNE
})
console.log(getinfo)
}
ln.disconnect()
In our terminal we check that we didn't introduce errors and
invoice.js
returns l2
's information:
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
id: '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7',
alias: 'VIOLETBOUNCE',
color: '0365a1',
num_peers: 2,
num_pending_channels: 0,
num_active_channels: 1,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 109,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
Now let the node l2
creates a new rune that allows only the method
invoice
:
◉ tony@tony:~/clnlive:
$ l2-cli commando-rune null '[["method=invoice"]]'
{
"rune": "KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==",
"unique_id": "2"
}
Let's decode it to be sure that the restriction has been applied to the rune:
◉ tony@tony:~/clnlive:
$ l2-cli -k decode string=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==
{
"type": "rune",
"unique_id": "2",
"string": "2a0c46620e2e4f374733f500ca8be315f7fbab55b9e5f6275fe0aea3c4bd7692:=2&method=invoice",
"restrictions": [
{
"alternatives": [
"method=invoice"
],
"summary": "method (of command) equal to 'invoice'"
}
],
"valid": true
}
In invoice.js
we replace the rune with this new created one that
allows only invoice
method
...
// rune only `invoice`
const RUNE = 'KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ=='
...
and by running invoice.js
script which try to run getinfo
method on
the node l2
we get the following Not authorized...
error:
◉ tony@tony:~/clnlive:
$ ./invoice.js
node:internal/process/esm_loader:94
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
Let's modify our if
statement in invoice.js
such that we do an
invoice
on l2
using commando
with an amount of 10000 msat, the label
being inv-0
and the description being Description
:
...
if (connected) {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: '10000',
label: 'inv-0',
description: 'Description'
},
rune: RUNE
})
console.log(invoice)
}
...
In our terminal we are now able to create an invoice on the node l2
:
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: '00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5',
expires_at: 1690470883,
bolt11: 'lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9',
payment_secret: '3915ea5fb71c2ce27ddd3ef62df9b3526713815feedaa7010d42d354bd73fa14',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
We can check that this invoice is indeed listed on the node l2
◉ tony@tony:~/clnlive:
$ l2-cli listinvoices
{
"invoices": [
{
"label": "inv-0",
"bolt11": "lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"amount_msat": 10000,
"status": "unpaid",
"description": "Description",
"expires_at": 1690470883
}
]
}
and let the node l1
pays that invoice:
◉ tony@tony:~/clnlive:
$ l1-cli pay lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9
{
"destination": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"created_at": 1689866126.410,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "ad3f0d115c17947cf84e439f820b2e3cba3cf0ba9fa263fba2c031c522158e1b",
"status": "complete"
}
COOL!
Let's make the label
of the invoice unique (making it random is enough
in our case to insure uniqueness) such that we can run multiple time
invoice.js
and create many invoices. In some way we want to spam l2
.
...
if (connected) {
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: '10000',
label: `inv-${Math.random()}`,
description: 'Description'
},
rune: RUNE
})
console.log(invoice)
}
...
Back to our terminal, we can generate a new invoice on the node l2
with a random label:
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: 'b11bb5daaf8b00c7eecf5d895861340182aaf1fcc96c27e366443e9e86e88780',
expires_at: 1690471023,
bolt11: 'lnbcrt100n1pjtjnl0sp5845wqv8xnfec6stlyzlswzqe5rhfetqd2kdvh6mdls3pgtltmedqpp5kydmtk403vqv0mk0tky4scf5qxp24u0ue9kz0cmxgslfaphgs7qqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqzu9wdpeqcm3qpyj25r4f6x0zk8rlmwtkrmm48ahykrm0tja456csawsc2enc2wanne4537vl07kkswsudp7e46hntupures7ssn6axqpuptyn9',
payment_secret: '3d68e030e69a738d417f20bf070819a0ee9cac0d559acbeb6dfc22142febde5a',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ l2-cli listinvoices
{
"invoices": [
{
"label": "inv-0",
"bolt11": "lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"amount_msat": 10000,
"status": "paid",
"pay_index": 1,
"amount_received_msat": 10000,
"paid_at": 1689866127,
"payment_preimage": "ad3f0d115c17947cf84e439f820b2e3cba3cf0ba9fa263fba2c031c522158e1b",
"description": "Description",
"expires_at": 1690470883
},
{
"label": "inv-0.28556700488076636",
"bolt11": "lnbcrt100n1pjtjnl0sp5845wqv8xnfec6stlyzlswzqe5rhfetqd2kdvh6mdls3pgtltmedqpp5kydmtk403vqv0mk0tky4scf5qxp24u0ue9kz0cmxgslfaphgs7qqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqzu9wdpeqcm3qpyj25r4f6x0zk8rlmwtkrmm48ahykrm0tja456csawsc2enc2wanne4537vl07kkswsudp7e46hntupures7ssn6axqpuptyn9",
"payment_hash": "b11bb5daaf8b00c7eecf5d895861340182aaf1fcc96c27e366443e9e86e88780",
"amount_msat": 10000,
"status": "unpaid",
"description": "Description",
"expires_at": 1690471023
}
]
}
As we have no rate restriction encoding in the rune we are using, we
can run invoice.js
as many time as we want. This can be done like
this:
◉ tony@tony:~/clnlive:
$ for i in {1..5}; do ./invoice.js; done
{...,bolt11: 'lnbcrt100n1...4atcvkkxcpsqkva3',...}
{...,bolt11: 'lnbcrt100n1...h87s0p4hgpfkt2na',...}
{...,bolt11: 'lnbcrt100n1...swncp7awqpsm4ahl',...}
{...,bolt11: 'lnbcrt100n1...hecz2puacqtn57u4',...}
{...,bolt11: 'lnbcrt100n1...e0pp9uf8cp8f3f9c',...}
And we can do it again:
◉ tony@tony:~/clnlive:
$ for i in {1..5}; do ./invoice.js; done
{...,bolt11: 'lnbcrt100n1...dn3zcpwuspm0uuy3',...}
{...,bolt11: 'lnbcrt100n1...fx95vsnugqtgjgtc',...}
{...,bolt11: 'lnbcrt100n1...0fafe2wtqpp58e6t',...}
{...,bolt11: 'lnbcrt100n1...ufhm7lgfqp7mv9lp',...}
{...,bolt11: 'lnbcrt100n1...8jue4v26cpnyquxr',...}
As we want to protect the node l2
against spams, we create a new rune from
the previous rune
KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==
by adding a rate restriction. Specifically, we want to allow only 5
invoice
requests per minute. We do this by running the following
command:
◉ tony@tony:~/clnlive:
$ l2-cli -k commando-rune rune=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ== \
► restrictions='[["rate=5"]]'
{
"rune": "wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU=",
"unique_id": "2"
}
We can decode that new rune and check that the rate restriction has be taken into account:
◉ tony@tony:~/clnlive:
$ l2-cli -k decode string=wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU=
{
"type": "rune",
"unique_id": "2",
"string": "c24b5402eb6d1168193603c79a587bf783952c19360cd2dbd01829cf27797ecd:=2&method=invoice&rate=5",
"restrictions": [
{
"alternatives": [
"method=invoice"
],
"summary": "method (of command) equal to 'invoice'"
},
{
"alternatives": [
"rate=5"
],
"summary": "rate (max per minute) equal to 5"
}
],
"valid": true
}
Let's use that new rune in invoice.js
:
...
// rune only `invoice` and rate=5
const RUNE = 'wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU='
...
In our terminal we can create 5 invoices almost instantaneously
◉ tony@tony:~/clnlive:
$ for i in {1..5}; do ./invoice.js; done
{...,bolt11: 'lnbcrt100n1...rz530spgq7gxu5y',...}
{...,bolt11: 'lnbcrt100n1...tdye8qeqqccz55h',...}
{...,bolt11: 'lnbcrt100n1...2u8yt4yqqvqz8sr',...}
{...,bolt11: 'lnbcrt100n1...y7x7lvlqpky6sfc',...}
{...,bolt11: 'lnbcrt100n1...hszqn7mgpess5yn',...}
but can't create more due to the rate limite encoded in the new rune:
◉ tony@tony:~/clnlive:
$ ./invoice.js
node:internal/process/esm_loader:94
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: Rate of 5 per minute exceeded'
}
If we wait 1 minute, we can check that we can generate a new invoice again:
◉ tony@tony:~/clnlive:
$ ./invoice.js
{..., bolt11: 'lnbcrt100n1...axwqagcq4l5a07',...}
Runes are really powerful!
To end this demo, we want invoice.js
script to take two positional
arguments:
the amount of the invoice in msat
and the description of the invoice.
We can do this like this:
if (connected) {
const [amountMsat, description] = process.argv.slice(2)
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: amountMsat,
label: `inv-${Math.random()}`,
description: description
},
rune: RUNE
})
console.log(invoice)
}
Now, we can create the following invoice of 10000 msat with the
description pizza
◉ tony@tony:~/clnlive:
$ ./invoice.js 10000 pizza
{
payment_hash: 'c3b4f056c38f9beab43d3e9f28aa587e2bfa54114d442e87d0fa7d849cbd8a82',
expires_at: 1690471534,
bolt11: 'lnbcrt100n1pjtj50wsp55cdvjyqu58um03w3nhsmp7tupq7ntyl3hxesfr40dtcuwwycqw0qpp5cw60q4kr37d74dpa860j32jc0c4l54q3f4zzap7slf7cf89a32pqdqgwp5h57npxqyjw5qcqp29qxpqysgqkaf45jua4xqgn69p35urlgr6erj7ma0e580vewk4app6n954yy59e3r8amsnzpyfg8uatvezz4wl4ym8rumc6egyks2tqx9lfhqda9gpm2ghzd',
payment_secret: 'a61ac9101ca1f9b7c5d19de1b0f97c083d3593f1b9b3048eaf6af1c73898039e',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
and finally let the node l1
pay that pizza:
◉ tony@tony:~/clnlive:
$ l1-cli pay lnbcrt100n1pjtj50wsp55cdvjyqu58um03w3nhsmp7tupq7ntyl3hxesfr40dtcuwwycqw0qpp5cw60q4kr37d74dpa860j32jc0c4l54q3f4zzap7slf7cf89a32pqdqgwp5h57npxqyjw5qcqp29qxpqysgqkaf45jua4xqgn69p35urlgr6erj7ma0e580vewk4app6n954yy59e3r8amsnzpyfg8uatvezz4wl4ym8rumc6egyks2tqx9lfhqda9gpm2ghzd
{
"destination": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"payment_hash": "c3b4f056c38f9beab43d3e9f28aa587e2bfa54114d442e87d0fa7d849cbd8a82",
"created_at": 1689866776.403,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "68fe9b9a362b94a6c24c4fe9eb641e733cbf255f85939d93e6debe854c5a968a",
"status": "complete"
}
We are done!
Terminal session
We ran the following commands in this order:
# TERMINAL 1
$ ls
$ source lightning/contrib/startup_regtest.sh
$ start_ln
$ alias l1-cli
$ connect 1 2
$ l1-cli listpeers
$ l1-cli commando-rune
$ l1-cli -k decode string=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
$ l1-cli getinfo | jq -r .id
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
$ fund_nodes
$ l1-cli listfunds
$ l2-cli newaddr
$ l2-cli -k commando peer_id=02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df method=withdraw params='{"destination": "bcrt1qcsk9r09shnmwaeqxckwvgklkmsuwrmvt8r6euu", "satoshi": "all"}' rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
$ bt-cli -rpcwallet=default getnewaddress
$ bt-cli generatetoaddress 1 bcrt1q8k3lut8j9nyh8wp7afwqzf88g6sdxtpdq5h0rc
$ l1-cli listfunds
$ l1-cli commando-listrunes
$ l1-cli -k commando-rune rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA== restrictions=readonly
$ l1-cli decode k6YhINmpUP99wRYmGA2x_gJjcDFhLDIzO3uU9bPu20E9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl
$ l1-cli commando-rune null readonly
$ l1-cli commando-listrunes
$ l1-cli commando-blacklist 0
$ l1-cli commando-listrunes
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
$ l2-cli getinfo | jq '.id, .binding'
$ alias l2-cli
$ npm i lnmessage
$ ./getinfo.js
$ l2-cli commando-rune null readonly
$ ./getinfo.js
$ ./invoice.js
$ l2-cli commando-rune null '[["method=invoice"]]'
$ l2-cli -k decode string=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==
$ ./invoice.js
$ ./invoice.js
$ l2-cli listinvoices
$ l1-cli pay lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9
$ ./invoice.js
$ l2-cli listinvoices
$ for i in {1..5}; do ./invoice.js; done
$ for i in {1..5}; do ./invoice.js; done
$ l2-cli -k commando-rune rune=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ== restrictions='[["rate=5"]]'
$ l2-cli -k decode string=wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU=
$ for i in {1..5}; do ./invoice.js; done
$ ./invoice.js
$ ./invoice.js
$ ./invoice.js 10000 pizza
$ l1-cli pay lnbcrt100n1pjtj50wsp55cdvjyqu58um03w3nhsmp7tupq7ntyl3hxesfr40dtcuwwycqw0qpp5cw60q4kr37d74dpa860j32jc0c4l54q3f4zzap7slf7cf89a32pqdqgwp5h57npxqyjw5qcqp29qxpqysgqkaf45jua4xqgn69p35urlgr6erj7ma0e580vewk4app6n954yy59e3r8amsnzpyfg8uatvezz4wl4ym8rumc6egyks2tqx9lfhqda9gpm2ghzd
And below you can read the terminal session (command lines and outputs):
# TERMINAL 1
◉ tony@tony:~/clnlive:
$ ls
clnlive-scratch/ lightning/ getinfo.js invoice.js notes.org
◉ tony@tony:~/clnlive:
$ 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:~/clnlive:
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 598681
[2] 598719
WARNING: eatmydata not found: instal it for faster testing
Commands:
l1-cli, l1-log,
l2-cli, l2-log,
bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/clnlive:
$ alias l1-cli
alias l1-cli='lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/clnlive:
$ connect 1 2
{
"id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"features": "08a0000a0269a2",
"direction": "out",
"address": {
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
}
◉ tony@tony:~/clnlive:
$ l1-cli listpeers
{
"peers": [
{
"id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:7272"
],
"features": "08a0000a0269a2"
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-rune
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"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:~/clnlive:
$ l1-cli -k decode string=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"type": "rune",
"unique_id": "0",
"string": "71355f48e7f7e28a066ba8005723a8ec697d48c4dac05a0e7f4c0f44df9b84c5:=0",
"restrictions": [],
"valid": true
}
◉ tony@tony:~/clnlive:
$ l1-cli getinfo | jq -r .id
02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df
◉ tony@tony:~/clnlive:
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"alias": "SLEEPYPHOTO",
"color": "02139b",
"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": 7171
}
],
"version": "v23.05.2",
"blockheight": 1,
"network": "regtest",
"fees_collected_msat": 0,
"lightning-dir": "/tmp/l1-regtest/regtest",
"our_features": {
"init": "08a0000a0269a2",
"node": "88a0000a0269a2",
"channel": "",
"invoice": "02000002024100"
}
}
◉ tony@tony:~/clnlive:
$ fund_nodes
Mining into address bcrt1qefqgq474z47e0mx8kpcwhltwhsr3n0nesg49c9... 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:~/clnlive:
$ l1-cli listfunds
{
"outputs": [
{
"txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"output": 0,
"amount_msat": 98999846000,
"scriptpubkey": "00147d3dc7584ba33b8bf92c11668357e29977d99f21",
"address": "bcrt1q057uwkzt5vach7fvz9ngx4lzn9man8epp8e5jc",
"status": "confirmed",
"blockheight": 103,
"reserved": false
}
],
"channels": [
{
"peer_id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"state": "CHANNELD_NORMAL",
"channel_id": "59e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1d",
"short_channel_id": "103x1x1",
"our_amount_msat": 1000000000,
"amount_msat": 1000000000,
"funding_txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"funding_output": 1
}
]
}
◉ tony@tony:~/clnlive:
$ l2-cli newaddr
{
"bech32": "bcrt1qcsk9r09shnmwaeqxckwvgklkmsuwrmvt8r6euu"
}
◉ tony@tony:~/clnlive:
$ l2-cli -k commando peer_id=02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df \
► method=withdraw \
► params='{"destination": "bcrt1qcsk9r09shnmwaeqxckwvgklkmsuwrmvt8r6euu", "satoshi": "all"}' \
► rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"tx": "020000000159e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1c0000000000fdffffff01b89de60500000000160014c42c51bcb0bcf6eee406c59cc45bf6dc38e1ed8b6c000000",
"txid": "f5493851d1ee0bfe410d426c45850d26a41411dd524ee430389451d7f4b5b274",
"psbt": "cHNidP8BAgQCAAAAAQMEbAAAAAEEAQEBBQEBAQYBAwH7BAIAAAAAAQDqAgAAAAABAZDi4Lh3qxCW2XZnhXIIdz1BF3lbYBtKlrklY2rTtfGfAAAAAAD9////Aiae5gUAAAAAFgAUfT3HWEujO4v5LBFmg1fimXfZnyFAQg8AAAAAACIAIE63d7Xc+sw1idoVUOMvIkeLz5SoRieGPZRwMnI0CJ2+AkcwRAIgWJvJVdV12C4Y2FusM7BVIfSSlJKR+ZKJdZ25GbynIfYCIBPApKs0nAENTd0H1p+5x9jtdkmnORx9rlaRoOlZGXiEASECBLOpXSa0gdYoygpFI2WqWorsuN6S1tpkorMHhv/mymdmAAAAAQEfJp7mBQAAAAAWABR9PcdYS6M7i/ksEWaDV+KZd9mfISICAkTdt1WBadiveioGL6SQAXheTDEWPYHoufQVkVtw4QxQRzBEAiBj+AcJ5+mmb0bnRmfmMzr9a9ecjq6IGRuI+SHUT4yongIgRVLsJ31TajY/mbWQxoZA+TDOl+htW4lsEt9P2bAVHDUBIgYCRN23VYFp2K96KgYvpJABeF5MMRY9gei59BWRW3DhDFAIfT3HWAAAAAABDiBZ4qiY/tavdeh5b1Ta79u76QcHH1Dog72UG7gDEONeHAEPBAAAAAABEAT9////AAEDCLid5gUAAAAAAQQWABTELFG8sLz27uQGxZzEW/bcOOHtiwz8CWxpZ2h0bmluZwQCAAEA"
}
◉ tony@tony:~/clnlive:
$ bt-cli -rpcwallet=default getnewaddress
bcrt1q8k3lut8j9nyh8wp7afwqzf88g6sdxtpdq5h0rc
◉ tony@tony:~/clnlive:
$ bt-cli generatetoaddress 1 bcrt1q8k3lut8j9nyh8wp7afwqzf88g6sdxtpdq5h0rc
[
"0c06ae63b41ba69c5a5885424d4cc1e0c351ecdae1fdf71c968b0b0dd8f4c048"
]
◉ tony@tony:~/clnlive:
$ l1-cli listfunds
{
"outputs": [],
"channels": [
{
"peer_id": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"connected": true,
"state": "CHANNELD_NORMAL",
"channel_id": "59e2a898fed6af75e8796f54daefdbbbe907071f50e883bd941bb80310e35e1d",
"short_channel_id": "103x1x1",
"our_amount_msat": 1000000000,
"amount_msat": 1000000000,
"funding_txid": "1c5ee31003b81b94bd83e8501f0707e9bbdbefda546f79e875afd6fe98a8e259",
"funding_output": 1
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli -k commando-rune rune=cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA== restrictions=readonly
{
"rune": "k6YhINmpUP99wRYmGA2x_gJjcDFhLDIzO3uU9bPu20E9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "0"
}
◉ tony@tony:~/clnlive:
$ l1-cli decode k6YhINmpUP99wRYmGA2x_gJjcDFhLDIzO3uU9bPu20E9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl
{
"type": "rune",
"unique_id": "0",
"string": "93a62120d9a950ff7dc11626180db1fe02637031612c32333b7b94f5b3eedb41:=0&method^list|method^get|method=summary&method/listdatastore",
"restrictions": [
{
"alternatives": [
"method^list",
"method^get",
"method=summary"
],
"summary": "method (of command) starts with 'list' OR method (of command) starts with 'get' OR method (of command) equal to 'summary'"
},
{
"alternatives": [
"method/listdatastore"
],
"summary": "method (of command) unequal to 'listdatastore'"
}
],
"valid": true
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-rune null readonly
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1"
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
},
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1",
"restrictions": [
{
"alternatives": [
{
"fieldname": "method",
"value": "list",
"condition": "^",
"english": "method starts with list"
},
{
"fieldname": "method",
"value": "get",
"condition": "^",
"english": "method starts with get"
},
{
"fieldname": "method",
"value": "summary",
"condition": "=",
"english": "method equal to summary"
}
],
"english": "method starts with list OR method starts with get OR method equal to summary"
},
{
"alternatives": [
{
"fieldname": "method",
"value": "listdatastore",
"condition": "/",
"english": "method unequal to listdatastore"
}
],
"english": "method unequal to listdatastore"
}
],
"restrictions_as_english": "method starts with list OR method starts with get OR method equal to summary AND method unequal to listdatastore"
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-blacklist 0
{
"blacklist": [
{
"start": 0,
"end": 0
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli commando-listrunes
{
"runes": [
{
"rune": "cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==",
"blacklisted": true,
"unique_id": "0",
"restrictions": [],
"restrictions_as_english": ""
},
{
"rune": "JoQiAUQc0e-480qdA_7kkMPIICbO9jJYSAWprrrWXuI9MSZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "1",
"restrictions": [
{
"alternatives": [
{
"fieldname": "method",
"value": "list",
"condition": "^",
"english": "method starts with list"
},
{
"fieldname": "method",
"value": "get",
"condition": "^",
"english": "method starts with get"
},
{
"fieldname": "method",
"value": "summary",
"condition": "=",
"english": "method equal to summary"
}
],
"english": "method starts with list OR method starts with get OR method equal to summary"
},
{
"alternatives": [
{
"fieldname": "method",
"value": "listdatastore",
"condition": "/",
"english": "method unequal to listdatastore"
}
],
"english": "method unequal to listdatastore"
}
],
"restrictions_as_english": "method starts with list OR method starts with get OR method equal to summary AND method unequal to listdatastore"
}
]
}
◉ tony@tony:~/clnlive:
$ l2-cli commando 02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df getinfo null cTVfSOf34ooGa6gAVyOo7Gl9SMTawFoOf0wPRN-bhMU9MA==
{
"code": 19537,
"message": "Not authorized: Blacklisted rune"
}
◉ tony@tony:~/clnlive:
$ l2-cli getinfo | jq '.id, .binding'
"0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7"
[
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7272
}
]
◉ tony@tony:~/clnlive:
$ alias l2-cli
alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive:
$ npm i lnmessage
added 9 packages in 4s
5 packages are looking for funding
run `npm fund` for details
◉ tony@tony:~/clnlive:
$ ./getinfo.js
^C
◉ tony@tony:~/clnlive:
$ l2-cli commando-rune null readonly
{
"rune": "9xepUI9PBgd8dJhPUoPnelh2GPRL1TSD3-nf_jdGrzo9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "0"
}
◉ tony@tony:~/clnlive:
$ ./getinfo.js
{
id: '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7',
alias: 'VIOLETBOUNCE',
color: '0365a1',
num_peers: 2,
num_pending_channels: 0,
num_active_channels: 1,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 109,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
id: '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7',
alias: 'VIOLETBOUNCE',
color: '0365a1',
num_peers: 2,
num_pending_channels: 0,
num_active_channels: 1,
num_inactive_channels: 0,
address: [],
binding: [ { type: 'ipv4', address: '127.0.0.1', port: 7272 } ],
version: 'v23.05.2',
blockheight: 109,
network: 'regtest',
fees_collected_msat: 0,
'lightning-dir': '/tmp/l2-regtest/regtest',
our_features: {
init: '08a0000a0269a2',
node: '88a0000a0269a2',
channel: '',
invoice: '02000002024100'
}
}
◉ tony@tony:~/clnlive:
$ l2-cli commando-rune null '[["method=invoice"]]'
{
"rune": "KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==",
"unique_id": "2"
}
◉ tony@tony:~/clnlive:
$ l2-cli -k decode string=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ==
{
"type": "rune",
"unique_id": "2",
"string": "2a0c46620e2e4f374733f500ca8be315f7fbab55b9e5f6275fe0aea3c4bd7692:=2&method=invoice",
"restrictions": [
{
"alternatives": [
"method=invoice"
],
"summary": "method (of command) equal to 'invoice'"
}
],
"valid": true
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
node:internal/process/esm_loader:94
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: method is not equal to invoice'
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: '00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5',
expires_at: 1690470883,
bolt11: 'lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9',
payment_secret: '3915ea5fb71c2ce27ddd3ef62df9b3526713815feedaa7010d42d354bd73fa14',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ l2-cli listinvoices
{
"invoices": [
{
"label": "inv-0",
"bolt11": "lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"amount_msat": 10000,
"status": "unpaid",
"description": "Description",
"expires_at": 1690470883
}
]
}
◉ tony@tony:~/clnlive:
$ l1-cli pay lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9
{
"destination": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"created_at": 1689866126.410,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "ad3f0d115c17947cf84e439f820b2e3cba3cf0ba9fa263fba2c031c522158e1b",
"status": "complete"
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: 'b11bb5daaf8b00c7eecf5d895861340182aaf1fcc96c27e366443e9e86e88780',
expires_at: 1690471023,
bolt11: 'lnbcrt100n1pjtjnl0sp5845wqv8xnfec6stlyzlswzqe5rhfetqd2kdvh6mdls3pgtltmedqpp5kydmtk403vqv0mk0tky4scf5qxp24u0ue9kz0cmxgslfaphgs7qqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqzu9wdpeqcm3qpyj25r4f6x0zk8rlmwtkrmm48ahykrm0tja456csawsc2enc2wanne4537vl07kkswsudp7e46hntupures7ssn6axqpuptyn9',
payment_secret: '3d68e030e69a738d417f20bf070819a0ee9cac0d559acbeb6dfc22142febde5a',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ l2-cli listinvoices
{
"invoices": [
{
"label": "inv-0",
"bolt11": "lnbcrt100n1pjtjnmrsp58y275hahrskwylwa8mmzm7dn2fn38q2lamd2wqgdgtf4f0tnlg2qpp5qpyr8qxsjra6r2wfw6yhje3vrt743afl0ta6nw907jmjv5e0ttjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm5mfzzjrdh5z9w9mmwl4nzhe49dd4rd956r2pw2m4pzdv28qjgen9dwa2hepkyu20lwkn47qsrjqg9uksu64v4h333prtn050lj2vwspvkxyn9",
"payment_hash": "00483380d090fba1a9c9768979662c1afd58f53f7afba9b8aff4b726532f5ae5",
"amount_msat": 10000,
"status": "paid",
"pay_index": 1,
"amount_received_msat": 10000,
"paid_at": 1689866127,
"payment_preimage": "ad3f0d115c17947cf84e439f820b2e3cba3cf0ba9fa263fba2c031c522158e1b",
"description": "Description",
"expires_at": 1690470883
},
{
"label": "inv-0.28556700488076636",
"bolt11": "lnbcrt100n1pjtjnl0sp5845wqv8xnfec6stlyzlswzqe5rhfetqd2kdvh6mdls3pgtltmedqpp5kydmtk403vqv0mk0tky4scf5qxp24u0ue9kz0cmxgslfaphgs7qqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqzu9wdpeqcm3qpyj25r4f6x0zk8rlmwtkrmm48ahykrm0tja456csawsc2enc2wanne4537vl07kkswsudp7e46hntupures7ssn6axqpuptyn9",
"payment_hash": "b11bb5daaf8b00c7eecf5d895861340182aaf1fcc96c27e366443e9e86e88780",
"amount_msat": 10000,
"status": "unpaid",
"description": "Description",
"expires_at": 1690471023
}
]
}
◉ tony@tony:~/clnlive:
$ for i in {1..5}; do ./invoice.js; done
{
payment_hash: '22d26f262d9661ea7d3a5b6de1770ec7e912e40d5f0325af461b0ae70aa2f2d6',
expires_at: 1690471065,
bolt11: 'lnbcrt100n1pjtj5qesp56p0n8jvf46gs6ykxcn66swcssfq39u6m72ads5qrllrvfg8erdsspp5ytfx7f3djes75lf6tdk7zacwcl539eqdtupjtt6xrv9wwz4z7ttqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqnnqgv8wpf0q7z5qe3a65ng3qc047mhm7m8276pscxjvw8lu8zqh8tc7pjnx62n9hu3lpnkvkg3ercuzw9g0jlm2qwneq3k4atcvkkxcpsqkva3',
payment_secret: 'd05f33c989ae910d12c6c4f5a83b10824112f35bf2bad85003ffc6c4a0f91b61',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: 'f3210ca2c6312712bfafb3cead7096a0db1da39d9d8014d880727f23f84d1ae2',
expires_at: 1690471066,
bolt11: 'lnbcrt100n1pjtj5q6sp5h7xgtp3myzce5hne7n72ahmeufnklxnqwj5uxxgvw5kpx9uumcxqpp57vssegkxxyn390a0k0826uyk5rd3mguankqpfkyqwflj87zdrt3qdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq5zmur44mqqrakyyd0tnd2h8zjlat7cexutz0j4tsrla5rqpq79fkmnspkyzxy8gw74ge3clcv7jc53fgquf8psp93mv3vzh87s0p4hgpfkt2na',
payment_secret: 'bf8c85863b20b19a5e79f4fcaedf79e2676f9a6074a9c3190c752c13179cde0c',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '16b01c1e7003c07bbc1ba1db080ec30a9fdbf2b3cc3ddec3e272e92ae133b78e',
expires_at: 1690471066,
bolt11: 'lnbcrt100n1pjtj5q6sp5q09zkznejwwc6advkwtn3x63ls7kvsu04v8ce9alxha7s2d6dx3qpp5z6cpc8nsq0q8h0qm58dssrkrp20ahu4nes7aaslzwt5j4cfnk78qdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq5shj6xgc36kydpnmzafkars3nxxmram2ndg7cpuhrqcw2y525pf9qeu0f08pdrd0hp7jzp3wp3lcrngpf0pwsw6c5valfmswncp7awqpsm4ahl',
payment_secret: '03ca2b0a79939d8d75acb397389b51fc3d66438fab0f8c97bf35fbe829ba69a2',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '801cfb72c1ccec60d61c3c55501342c201dd96775536b498dfb23fe4757ca220',
expires_at: 1690471067,
bolt11: 'lnbcrt100n1pjtj5qmsp593mdwt4dx4s8ez3f344lwasj8suxw947qhvgvrzg7z9pvrj29g2spp5sqw0kukpenkxp4su8324qy6zcgqam9nh25mtfxxlkgl7gatu5gsqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq8s8gn49pe0uk2ln9l2wreqlmh4ea4ae4gr6h4tzvx2g2438hvempckm2jlq5c97v8zdn2phsv653wvg2v4k07gdhyvak4fhecz2puacqtn57u4',
payment_secret: '2c76d72ead35607c8a298d6bf776123c386716be05d8860c48f08a160e4a2a15',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: 'dbfb3af0f1aa33932e6e5277132ab7892fa8fe714646db69f8ecc0e45ec769f2',
expires_at: 1690471067,
bolt11: 'lnbcrt100n1pjtj5qmsp5utuzs6667t0rjdcgtjc5wlj3j0qnn35zm9mzquylzg50q0eacwlqpp5m0an4u834geextnw2fm3x24h3yh63ln3gerdk60canqwghk8d8eqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqx6g88n6e60n8ayvva8aswfuumvrhsd9vpagsa3g5y8nwmulcsmmscf4zmz42vkdqft7kujzqytfr8qmexs9859fhm447nue0pp9uf8cp8f3f9c',
payment_secret: 'e2f8286b5af2de3937085cb1477e5193c139c682d97620709f1228f03f3dc3be',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ for i in {1..5}; do ./invoice.js; done
{
payment_hash: '6fd136adc408f1613a35a83730d483eb673d493f0c30f8e87ba75bd8e26b3dff',
expires_at: 1690471072,
bolt11: 'lnbcrt100n1pjtj5pqsp5fkq8lg80ytflncgy5v7taln25y2fddanxrkykha4vp7vfr87gggqpp5dlgndtwyprckzw344qmnp4yradnn6jflpsc036rm5ada3cnt8hlsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqwjrekw9nfk7pxrgdnxmaryxmrgp796k74lw8vdjngmfksd6q5vs3s8cnpd60mjz86rplxzzz2u7809sfywcf8f969y6aakdn3zcpwuspm0uuy3',
payment_secret: '4d807fa0ef22d3f9e104a33cbefe6aa11496b7b330ec4b5fb5607cc48cfe4210',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '531733963c102db1432a5dedf665ed767368afe22cd93c0383385d5bf91fb534',
expires_at: 1690471072,
bolt11: 'lnbcrt100n1pjtj5pqsp5mk3waty734t4wwhalty00shk3etrt2fdqqz2w8xx2r94t58va2rqpp52vtn893uzqkmzse2thklve0dweek3tlz9nvncqur8pw4h7glk56qdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq4mmnccnf4mzhr0l0mdpuk45u7fnq259yr77lk9fcq6zzddl2lhvrqcj92p7z4des6z76a68sz532dap5hvdr0f92n7pu55fx95vsnugqtgjgtc',
payment_secret: 'dda2eeac9e8d57573afdfac8f7c2f68e5635a92d0004a71cc650cb55d0ecea86',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '6bfc6395dfc8e0251fc1c0264ee95a441a0c2b0be2c6f52bb8b89ef2b4a0d3f9',
expires_at: 1690471073,
bolt11: 'lnbcrt100n1pjtj5ppsp582r4crj2vpecve8wqpfjhh0dvulmtyezq4w8503qzfqg3nksus5qpp5d07x89wlersz287pcqnya626gsdqc2ctutr022achz009d9q60usdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqf8ytfflgl84tjst3tjphjn9e8yck79fwzpc8h7l54v03yjec5vqn29ltyr08gmvpzpgg4kf2w0xm0j4f40c8rwh9y44l650fafe2wtqpp58e6t',
payment_secret: '3a875c0e4a60738664ee00532bdded673fb59322055c7a3e20124088ced0e428',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: 'fa5468d5a9654cb397d366df3280d5c4a4dcebaa217c25cd5dfc66fd9be3c988',
expires_at: 1690471073,
bolt11: 'lnbcrt100n1pjtj5ppsp5v4hgmvpwkgzj2zl74xqml6p322t8vv9u4unfdqujl9dr0x058zgqpp5lf2x34dfv4xt897nvm0n9qx4cjjde6a2y97ztn2al3n0mxlrexyqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq55fh4yqykjk8z3mxamg92adnjmua386d72f9dd4qea2cmtwk0dzj9theyjk3npxxatnp7tesceu4dhzn4d630p4cq2f75yufhm7lgfqp7mv9lp',
payment_secret: '656e8db02eb205250bfea981bfe83152967630bcaf26968392f95a3799f43890',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '39356e5fba55b1458bbdb92776e029684fd7a7b355962ca727b77adaa0e983a5',
expires_at: 1690471074,
bolt11: 'lnbcrt100n1pjtj5pzsp5y8tfpkwq5cljzjpd7537ktrpgchveqkd2wxclmaql3tmxrdvcxrqpp58y6kuha62kc5tzaahynhdcpfdp8a0fan2ktzefe8kaad4g8fswjsdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq9z9yuy0h9wlg7j48rucmr3yvt7eg9nh8uhv2k2gtjaahu8txenu44jn992e5gzpcrdj4km7aqyp400n2d5vh279hl56sm98jue4v26cpnyquxr',
payment_secret: '21d690d9c0a63f21482df523eb2c61462ecc82cd538d8fefa0fc57b30dacc186',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ l2-cli -k commando-rune rune=KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ== restrictions='[["rate=5"]]'
{
"rune": "wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU=",
"unique_id": "2"
}
◉ tony@tony:~/clnlive:
$ l2-cli -k decode string=wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU=
{
"type": "rune",
"unique_id": "2",
"string": "c24b5402eb6d1168193603c79a587bf783952c19360cd2dbd01829cf27797ecd:=2&method=invoice&rate=5",
"restrictions": [
{
"alternatives": [
"method=invoice"
],
"summary": "method (of command) equal to 'invoice'"
},
{
"alternatives": [
"rate=5"
],
"summary": "rate (max per minute) equal to 5"
}
],
"valid": true
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: '0dfc3229c67fc082f1f04dd9444ceab6133015b1665b12b3338f1824af6b8b50',
expires_at: 1690471329,
bolt11: 'lnbcrt100n1pjtj5fpsp5q2rkpw0q0yjhd4yc9f2he5anfz7u5eke4cryd3pyt3hv64kwx36qpp5ph7ry2wx0lqg9u0sfhv5gn82kcfnq9d3ved39ven3uvzftmt3dgqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqhtlr75n5ysy6f2x8v72w5fxd7a0932t4xehxnc4csmwyzhtsnrwrdcwhef5kukprztsvvmzjamgpdszdntum9wy53u0f6ytrz530spgq7gxu5y',
payment_secret: '028760b9e0792576d4982a557cd3b348bdca66d9ae0646c4245c6ecd56ce3474',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ for i in {1..4}; do ./invoice.js; done
{
payment_hash: 'fc85941b31d5ea4046a8b78d27b93b7e3d39267b70ecd35911ee0dc8b0fdf30b',
expires_at: 1690471342,
bolt11: 'lnbcrt100n1pjtj5fwsp5x2wl0u0ldsse82t0xul5f32ftn59pyh8gxm2lxptg7qvt7ufre4qpp5ljzegxe36h4yq34gk7xj0wfm0c7njfnmwrkdxkg3acxu3v8a7v9sdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq756gczk0rvseegze95yraykwnf95sq040wzh9r9n33r2qa2p5yujsdfpwzrhj4kuqjs5n7xu547cydhnchtqnjahfg8s8lutdye8qeqqccz55h',
payment_secret: '329df7f1ff6c2193a96f373f44c5495ce85092e741b6af982b4780c5fb891e6a',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: '6652e61f380abf47473c7135cad782f938ef2ae90201ec28ff208f54944b5c21',
expires_at: 1690471343,
bolt11: 'lnbcrt100n1pjtj5f0sp5ykl4p09tmsgwqynresswltrshympynmqm55zqny5zv5vq7829dyspp5vefwv8ecp2l5w3euwy6u44uzlyuw72hfqgq7c28lyz84f9zttsssdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqfrx83erpp7vqhxf2dukr95r0amalzw5ljx8yj87wuxaqy9dwnyszusferelkj4lxj4nt8t07lfzg27fs5wct0t6q7uy2psr2u8yt4yqqvqz8sr',
payment_secret: '25bf50bcabdc10e01263cc20efac70b936124f60dd28204c941328c078ea2b49',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: 'dcfb803fbd5c20ff1519e0b64c72055a760ac04eaf488e9748c494520af6fca8',
expires_at: 1690471343,
bolt11: 'lnbcrt100n1pjtj5f0sp5whh78aey955e26uf96gz8czajtal39jkk4nej7uv32n4ql3u5l2spp5mnacq0aatss079geuzmycus9tfmq4szw4ayga96gcj29yzhklj5qdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgq6lfdcd9gyzsx8tvntz5q5wyrdqrlkqunyer8q3u242lw5dk3h2y5ecfpup5mn4an9rk8yaghvlarhmfwtu2qfs488l3457ky7x7lvlqpky6sfc',
payment_secret: '75efe3f7242d29956b892e9023e05d92fbf89656b567997b8c8aa7507e3ca7d5',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
{
payment_hash: 'a621134f1bf1b1421a02bffbced8c465f41819de1f3aaadcc3b9413697e61b6e',
expires_at: 1690471344,
bolt11: 'lnbcrt100n1pjtj5fssp5dy6fkx0f3havnt5y533v4ttpqlkmnqt8xz9vzsk3dzyjtsejt4mqpp55cs3xncm7xc5yxszhlauakxyvh6psxw7rua24hxrh9qnd9lxrdhqdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqm6g4ylzuf26edpu3c8hcwucs5yj00wwszp5rqr07zekl8rm8468jjr3wsqfsu0uefn82xeuzgk6gemy20pre0z8ftxadpjzhszqn7mgpess5yn',
payment_secret: '69349b19e98dfac9ae84a462caad6107edb98167308ac142d1688925c3325d76',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
node:internal/process/esm_loader:94
internalBinding('errors').triggerUncaughtException(
^
{
code: 19537,
message: 'Not authorized: Rate of 5 per minute exceeded'
}
◉ tony@tony:~/clnlive:
$ ./invoice.js
{
payment_hash: '3f66fc5b60e9b07db2425c24983e035ff1a6a4dc4197bc8acf7d601a466237aa',
expires_at: 1690471446,
bolt11: 'lnbcrt100n1pjtj5vksp54e4vxc4fryn6mm6rv55dkgqd5jl0rdxw0lv87gfht3k9wmqwuwrspp58an0ckmqaxc8mvjztsjfs0srtlc6dfxugxtmezk004sp53nzx74qdqjg3jhxcmjd9c8g6t0dcxqyjw5qcqp29qxpqysgqgf09csj3r98g5u69hnghzau6qtls0uj3d3q737qfu6r6md76kmanq9lsj7ypn2jjqsphm4af2gzdlkff4zdva3dj2d37p3tuaxwqagcq4l5a07',
payment_secret: 'ae6ac362a91927adef436528db200da4bef1b4ce7fd87f21375c6c576c0ee387',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ ./invoice.js 10000 pizza
{
payment_hash: 'c3b4f056c38f9beab43d3e9f28aa587e2bfa54114d442e87d0fa7d849cbd8a82',
expires_at: 1690471534,
bolt11: 'lnbcrt100n1pjtj50wsp55cdvjyqu58um03w3nhsmp7tupq7ntyl3hxesfr40dtcuwwycqw0qpp5cw60q4kr37d74dpa860j32jc0c4l54q3f4zzap7slf7cf89a32pqdqgwp5h57npxqyjw5qcqp29qxpqysgqkaf45jua4xqgn69p35urlgr6erj7ma0e580vewk4app6n954yy59e3r8amsnzpyfg8uatvezz4wl4ym8rumc6egyks2tqx9lfhqda9gpm2ghzd',
payment_secret: 'a61ac9101ca1f9b7c5d19de1b0f97c083d3593f1b9b3048eaf6af1c73898039e',
warning_deadends: 'Insufficient incoming capacity, once dead-end peers were excluded'
}
◉ tony@tony:~/clnlive:
$ l1-cli pay lnbcrt100n1pjtj50wsp55cdvjyqu58um03w3nhsmp7tupq7ntyl3hxesfr40dtcuwwycqw0qpp5cw60q4kr37d74dpa860j32jc0c4l54q3f4zzap7slf7cf89a32pqdqgwp5h57npxqyjw5qcqp29qxpqysgqkaf45jua4xqgn69p35urlgr6erj7ma0e580vewk4app6n954yy59e3r8amsnzpyfg8uatvezz4wl4ym8rumc6egyks2tqx9lfhqda9gpm2ghzd
{
"destination": "0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7",
"payment_hash": "c3b4f056c38f9beab43d3e9f28aa587e2bfa54114d442e87d0fa7d849cbd8a82",
"created_at": 1689866776.403,
"parts": 1,
"amount_msat": 10000,
"amount_sent_msat": 10000,
"payment_preimage": "68fe9b9a362b94a6c24c4fe9eb641e733cbf255f85939d93e6debe854c5a968a",
"status": "complete"
}
# TERMINAL 2
◉ tony@tony:~/clnlive:
$ alias l2-cli='lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive:
$ l2-cli listpeers
{
"peers": [
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"connected": true,
"num_channels": 1,
"netaddr": [
"127.0.0.1:50508"
],
"features": "08a0000a0269a2"
}
]
}
◉ tony@tony:~/clnlive:
$ l2-cli listpeers
{
"peers": [
{
"id": "0390d494b9b1f2b396ad79f7069b5230b6a7247c565926d9e6b077739d583c0855",
"connected": true,
"num_channels": 0,
"netaddr": [
"127.0.0.1:48452"
],
"features": "0000000000000000"
},
{
"id": "02139b8774bf4b02881154c1753e49cc6bb6632593b3c3839bc073159d02c672df",
"connected": true,
"num_channels": 1,
"netaddr": [
"127.0.0.1:50508"
],
"features": "08a0000a0269a2"
}
]
}
Source code
getinfo.js
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
// node l2
const NODE_ID = '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7'
const NODE_IP = '127.0.0.1'
const NODE_PORT = '7272'
// rune
const RUNE = '9xepUI9PBgd8dJhPUoPnelh2GPRL1TSD3-nf_jdGrzo9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl'
const ln = new LnMessage({
remoteNodePublicKey: NODE_ID,
tcpSocket: new net.Socket(),
ip: NODE_IP,
port: NODE_PORT
})
await ln.connect()
const getinfo = await ln.commando({
method: 'getinfo',
params: [],
rune: RUNE
})
console.log(getinfo)
ln.disconnect()
invoice.js
#!/usr/bin/env node
import LnMessage from 'lnmessage'
import net from 'net'
// node l2
const NODE_ID = '0365a17e5faee910000a822f74deb926bda01671debfe46bbc66dddd3c18ae8ad7'
const NODE_IP = '127.0.0.1'
const NODE_PORT = '7272'
// rune only `invoice`
// const RUNE = 'KgxGYg4uTzdHM_UAyovjFff7q1W55fYnX-Cuo8S9dpI9MiZtZXRob2Q9aW52b2ljZQ=='
// rune only `invoice` and rate=5
const RUNE = 'wktUAuttEWgZNgPHmlh794OVLBk2DNLb0Bgpzyd5fs09MiZtZXRob2Q9aW52b2ljZSZyYXRlPTU='
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)
const invoice = await ln.commando({
method: 'invoice',
params: {
amount_msat: amountMsat,
label: `inv-${Math.random()}`,
description: description
},
rune: RUNE
})
console.log(invoice)
}
ln.disconnect()
package.json
{
"type": "module",
"dependencies": {
"lnmessage": "^0.2.2"
}
}