lnd/docs/grpc/javascript.md
Wilmer Paulino dc3db4bf62 docs: update path to where macaroons are stored
Co-Authored-By: Karl Ranna <karl@karlranna.com>
2018-08-23 18:05:00 -07:00

7.0 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:

var grpc = require('grpc');
var 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
var lndCert = fs.readFileSync("~/.lnd/tls.cert");
var credentials = grpc.credentials.createSsl(lndCert);
var lnrpcDescriptor = grpc.load("rpc.proto");
var lnrpc = lnrpcDescriptor.lnrpc;
var 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) {
  	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_peers: 1,
  block_height: 1006,
  block_hash: '198ba1dc43b4190e507fa5c7aea07a74ec0009a9ab308e1736dbdab5c767ff8e',
  synced_to_chain: false,
  testnet: false,
  chains: [ 'bitcoin' ] }

Response-streaming RPC

var 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:10009and 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
var async = require('async');
var _ = require('lodash');
var ByteBuffer = require('bytebuffer');

var dest_pubkey = <RECEIVER_ID_PUBKEY>;
var dest_pubkey_bytes = ByteBuffer.fromHex(dest_pubkey);

// Set a listener on the bidirectional stream
var 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);
  };
}
var payment_senders = [];
for (var 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.

var fs = require('fs');
var 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
var m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
var macaroon = m.toString('hex');
var meta = new grpc.Metadata().add('macaroon', macaroon);

var lnrpcDescriptor = grpc.load("rpc.proto");
var lnrpc = lnrpcDescriptor.lnrpc;
var 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.

var fs = require('fs');
var 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
var m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
var macaroon = m.toString('hex');

// build meta data credentials
var metadata = new grpc.Metadata()
metadata.add('macaroon', macaroon)
var macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => {
  callback(null, metadata);
});

// build ssl credentials using the cert the same as before
var lndCert = fs.readFileSync("~/.lnd/tls.cert");
var sslCreds = grpc.credentials.createSsl(lndCert);

// combine the cert credentials and the macaroon auth credentials
// such that every call is properly encrypted and authenticated
var credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);

// Pass the crendentials when creating a channel
var lnrpcDescriptor = grpc.load("rpc.proto");
var lnrpc = lnrpcDescriptor.lnrpc;
var client = new lnrpc.Lightning('some.address:10009', credentials);

client.getInfo({}, (err, res) => { ... });

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.