7.1 KiB
How to write a simple lnd
client in Javascript using node.js
Setup and Installation
First, you'll need to initialize a simple nodejs project:
npm init (or npm init -f if you want to use the default values without prompt)
Then you need to install the Javascript grpc library dependency:
npm install grpc --save
You also need to copy the lnd
rpc.proto
file in your project directory (or
at least somewhere reachable by your Javascript code).
The rpc.proto
file is located in the lnrpc
directory of the lnd
sources.
In order for the auto-generated code to compile successfully, you'll need to comment out the following line:
//import "google/api/annotations.proto";
Imports and Client
Every time you work with Javascript gRPC, you will have to import grpc
, load
rpc.proto
, and create a connection to your client like so:
const grpc = require('grpc');
const fs = require("fs");
// Due to updated ECDSA generated tls.cert we need to let gprc know that
// we need to use that cipher suite otherwise there will be a handhsake
// error when we communicate with the lnd rpc server.
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// Lnd cert is at ~/.lnd/tls.cert on Linux and
// ~/Library/Application Support/Lnd/tls.cert on Mac
let lndCert = fs.readFileSync("~/.lnd/tls.cert");
let credentials = grpc.credentials.createSsl(lndCert);
let lnrpcDescriptor = grpc.load("rpc.proto");
let lnrpc = lnrpcDescriptor.lnrpc;
let lightning = new lnrpc.Lightning('localhost:10009', credentials);
Examples
Let's walk through some examples of Javascript gRPC clients. These examples
assume that you have at least two lnd
nodes running, the RPC location of one
of which is at the default localhost:10009
, with an open channel between the
two nodes.
Simple RPC
lightning.getInfo({}, function(err, response) {
if (err) {
console.log('Error: ' + err);
}
console.log('GetInfo:', response);
});
You should get something like this in your console:
GetInfo: { identity_pubkey: '03c892e3f3f077ea1e381c081abb36491a2502bc43ed37ffb82e264224f325ff27',
alias: '',
num_pending_channels: 0,
num_active_channels: 1,
num_inactive_channels: 0,
num_peers: 1,
block_height: 1006,
block_hash: '198ba1dc43b4190e507fa5c7aea07a74ec0009a9ab308e1736dbdab5c767ff8e',
synced_to_chain: false,
testnet: false,
chains: [ 'bitcoin' ] }
Response-streaming RPC
let call = lightning.subscribeInvoices({});
call.on('data', function(invoice) {
console.log(invoice);
})
.on('end', function() {
// The server has finished sending
})
.on('status', function(status) {
// Process status
console.log("Current status" + status);
});
Now, create an invoice for your node at localhost:10009
and send a payment to
it from another node.
$ lncli addinvoice --amt=100
{
"r_hash": <RHASH>,
"pay_req": <PAYMENT_REQUEST>
}
$ lncli sendpayment --pay_req=<PAYMENT_REQUEST>
Your Javascript console should now display the details of the recently satisfied invoice.
Bidirectional-streaming RPC
This example has a few dependencies:
npm install --save async lodash bytebuffer
You can run the following in your shell or put it in a program and run it like
node script.js
// Load some libraries specific to this example
const async = require('async');
const _ = require('lodash');
const ByteBuffer = require('bytebuffer');
let dest_pubkey = <RECEIVER_ID_PUBKEY>;
let dest_pubkey_bytes = ByteBuffer.fromHex(dest_pubkey);
// Set a listener on the bidirectional stream
let call = lightning.sendPayment();
call.on('data', function(payment) {
console.log("Payment sent:");
console.log(payment);
});
call.on('end', function() {
// The server has finished
console.log("END");
});
// You can send single payments like this
call.write({ dest: dest_pubkey_bytes, amt: 6969 });
// Or send a bunch of them like this
function paymentSender(destination, amount) {
return function(callback) {
console.log("Sending " + amount + " satoshis");
console.log("To: " + destination);
call.write({
dest: destination,
amt: amount
});
_.delay(callback, 2000);
};
}
let payment_senders = [];
for (let i = 0; i < 10; i++) {
payment_senders[i] = paymentSender(dest_pubkey_bytes, 100);
}
async.series(payment_senders, function() {
call.end();
});
This example will send a payment of 100 satoshis every 2 seconds.
Using Macaroons
To authenticate using macaroons you need to include the macaroon in the metadata of the request.
const fs = require('fs');
const grpc = require('grpc');
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and
// ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac
let m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
let macaroon = m.toString('hex');
let meta = new grpc.Metadata().add('macaroon', macaroon);
let lnrpcDescriptor = grpc.load("rpc.proto");
let lnrpc = lnrpcDescriptor.lnrpc;
let client = new lnrpc.Lightning('some.address:10009', grpc.credentials.createInsecure());
client.getInfo({}, meta);
However, this can get tiresome to do for each request, so to avoid explicitly including the macaroon we can update the credentials to include it automatically.
const fs = require('fs');
const grpc = require('grpc');
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and
// ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac
let m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
let macaroon = m.toString('hex');
// build meta data credentials
let metadata = new grpc.Metadata()
metadata.add('macaroon', macaroon)
let macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => {
callback(null, metadata);
});
// build ssl credentials using the cert the same as before
let lndCert = fs.readFileSync("~/.lnd/tls.cert");
let sslCreds = grpc.credentials.createSsl(lndCert);
// combine the cert credentials and the macaroon auth credentials
// such that every call is properly encrypted and authenticated
let credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
// Pass the crendentials when creating a channel
let lnrpcDescriptor = grpc.load("rpc.proto");
let lnrpc = lnrpcDescriptor.lnrpc;
let client = new lnrpc.Lightning('some.address:10009', credentials);
client.getInfo({}, (err, response) => {
if (err) {
console.log('Error: ' + err);
}
console.log('GetInfo:', response);
});
Conclusion
With the above, you should have all the lnd
related gRPC
dependencies
installed locally in your project. In order to get up to speed with protofbuf
usage from Javascript, see this official protobuf
reference for
Javascript.
Additionally, this official gRPC
resource provides more
details around how to drive gRPC
from node.js
.