2017-03-28 01:20:31 +02:00
|
|
|
# How to write a simple `lnd` client in Javascript using `node.js`
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
## Setup and Installation
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2017-08-04 01:18:15 +02:00
|
|
|
First, you'll need to initialize a simple nodejs project:
|
2017-03-01 01:13:55 +01:00
|
|
|
```
|
|
|
|
npm init (or npm init -f if you want to use the default values without prompt)
|
|
|
|
```
|
|
|
|
|
2020-05-04 09:57:18 +02:00
|
|
|
Then you need to install the Javascript grpc and proto loader library
|
|
|
|
dependencies:
|
2017-03-01 01:13:55 +01:00
|
|
|
```
|
2020-05-04 09:57:18 +02:00
|
|
|
npm install grpc @grpc/proto-loader --save
|
2017-03-01 01:13:55 +01:00
|
|
|
```
|
|
|
|
|
2017-03-01 01:22:02 +01:00
|
|
|
You also need to copy the `lnd` `rpc.proto` file in your project directory (or
|
|
|
|
at least somewhere reachable by your Javascript code).
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2017-03-01 01:22:02 +01:00
|
|
|
The `rpc.proto` file is [located in the `lnrpc` directory of the `lnd`
|
|
|
|
sources](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto).
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
### Imports and Client
|
2017-08-04 01:18:15 +02:00
|
|
|
|
|
|
|
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:
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2017-03-01 01:22:02 +01:00
|
|
|
```js
|
2020-05-04 09:59:59 +02:00
|
|
|
const grpc = require('grpc');
|
2020-05-04 09:57:18 +02:00
|
|
|
const protoLoader = require('@grpc/proto-loader');
|
2020-05-04 09:59:59 +02:00
|
|
|
const fs = require("fs");
|
2017-08-09 07:30:51 +02:00
|
|
|
|
2018-04-18 03:42:55 +02:00
|
|
|
// 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'
|
|
|
|
|
2020-05-04 09:57:18 +02:00
|
|
|
// We need to give the proto loader some extra options, otherwise the code won't
|
|
|
|
// fully work with lnd.
|
|
|
|
const loaderOptions = {
|
|
|
|
keepCase: true,
|
|
|
|
longs: String,
|
|
|
|
enums: String,
|
|
|
|
defaults: true,
|
|
|
|
oneofs: true
|
|
|
|
};
|
|
|
|
const packageDefinition = protoLoader.loadSync('rpc.proto', loaderOptions);
|
|
|
|
|
2017-08-09 07:30:51 +02:00
|
|
|
// Lnd cert is at ~/.lnd/tls.cert on Linux and
|
|
|
|
// ~/Library/Application Support/Lnd/tls.cert on Mac
|
2020-05-04 09:59:59 +02:00
|
|
|
let lndCert = fs.readFileSync("~/.lnd/tls.cert");
|
|
|
|
let credentials = grpc.credentials.createSsl(lndCert);
|
2020-05-04 09:57:18 +02:00
|
|
|
let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
2020-05-04 09:59:59 +02:00
|
|
|
let lnrpc = lnrpcDescriptor.lnrpc;
|
|
|
|
let lightning = new lnrpc.Lightning('localhost:10009', credentials);
|
2017-08-04 01:18:15 +02:00
|
|
|
```
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
## Examples
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2017-08-09 07:30:51 +02:00
|
|
|
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.
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
### Simple RPC
|
2017-03-01 01:13:55 +01:00
|
|
|
|
2017-08-04 01:18:15 +02:00
|
|
|
```js
|
2020-05-04 09:59:59 +02:00
|
|
|
lightning.getInfo({}, function(err, response) {
|
|
|
|
if (err) {
|
|
|
|
console.log('Error: ' + err);
|
|
|
|
}
|
|
|
|
console.log('GetInfo:', response);
|
|
|
|
});
|
2017-03-01 01:13:55 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
You should get something like this in your console:
|
|
|
|
|
|
|
|
```
|
|
|
|
GetInfo: { identity_pubkey: '03c892e3f3f077ea1e381c081abb36491a2502bc43ed37ffb82e264224f325ff27',
|
|
|
|
alias: '',
|
|
|
|
num_pending_channels: 0,
|
2017-08-04 01:18:15 +02:00
|
|
|
num_active_channels: 1,
|
2018-08-28 18:25:55 +02:00
|
|
|
num_inactive_channels: 0,
|
2017-08-04 01:18:15 +02:00
|
|
|
num_peers: 1,
|
|
|
|
block_height: 1006,
|
|
|
|
block_hash: '198ba1dc43b4190e507fa5c7aea07a74ec0009a9ab308e1736dbdab5c767ff8e',
|
|
|
|
synced_to_chain: false,
|
|
|
|
testnet: false,
|
|
|
|
chains: [ 'bitcoin' ] }
|
|
|
|
```
|
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
### Response-streaming RPC
|
2017-08-04 01:18:15 +02:00
|
|
|
|
|
|
|
```js
|
2020-05-04 09:59:59 +02:00
|
|
|
let call = lightning.subscribeInvoices({});
|
2017-08-04 01:18:15 +02:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
```
|
2017-08-09 07:30:51 +02:00
|
|
|
|
|
|
|
Now, create an invoice for your node at `localhost:10009`and send a payment to
|
|
|
|
it from another node.
|
|
|
|
```bash
|
2018-01-18 19:32:24 +01:00
|
|
|
$ lncli addinvoice --amt=100
|
2017-08-09 07:30:51 +02:00
|
|
|
{
|
|
|
|
"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
|
2017-08-04 01:18:15 +02:00
|
|
|
invoice.
|
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
### Bidirectional-streaming RPC
|
2017-08-04 01:18:15 +02:00
|
|
|
|
|
|
|
This example has a few dependencies:
|
|
|
|
```shell
|
|
|
|
npm install --save async lodash bytebuffer
|
2017-03-01 01:13:55 +01:00
|
|
|
```
|
|
|
|
|
2017-08-04 01:18:15 +02:00
|
|
|
You can run the following in your shell or put it in a program and run it like
|
|
|
|
`node script.js`
|
|
|
|
|
|
|
|
```js
|
|
|
|
// Load some libraries specific to this example
|
2020-05-04 09:59:59 +02:00
|
|
|
const async = require('async');
|
|
|
|
const _ = require('lodash');
|
|
|
|
const ByteBuffer = require('bytebuffer');
|
2017-08-04 01:18:15 +02:00
|
|
|
|
2020-05-04 09:59:59 +02:00
|
|
|
let dest_pubkey = <RECEIVER_ID_PUBKEY>;
|
|
|
|
let dest_pubkey_bytes = ByteBuffer.fromHex(dest_pubkey);
|
2017-08-04 01:18:15 +02:00
|
|
|
|
|
|
|
// Set a listener on the bidirectional stream
|
2020-05-04 09:59:59 +02:00
|
|
|
let call = lightning.sendPayment();
|
2017-08-04 01:18:15 +02:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|
2020-05-04 09:59:59 +02:00
|
|
|
let payment_senders = [];
|
|
|
|
for (let i = 0; i < 10; i++) {
|
2017-08-04 01:18:15 +02:00
|
|
|
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.
|
|
|
|
|
2018-04-18 03:42:55 +02:00
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
### Using Macaroons
|
2018-04-18 03:42:55 +02:00
|
|
|
|
2020-05-04 10:07:54 +02:00
|
|
|
To authenticate using macaroons you need to include the macaroon in the metadata
|
|
|
|
of each request.
|
2018-04-18 03:42:55 +02:00
|
|
|
|
2020-05-04 10:07:54 +02:00
|
|
|
The following snippet will add the macaroon to every request automatically:
|
2018-04-18 03:42:55 +02:00
|
|
|
|
|
|
|
```js
|
2020-05-04 09:59:59 +02:00
|
|
|
const fs = require('fs');
|
|
|
|
const grpc = require('grpc');
|
2020-05-04 09:57:18 +02:00
|
|
|
const protoLoader = require('@grpc/proto-loader');
|
|
|
|
const loaderOptions = {
|
|
|
|
keepCase: true,
|
|
|
|
longs: String,
|
|
|
|
enums: String,
|
|
|
|
defaults: true,
|
|
|
|
oneofs: true
|
|
|
|
};
|
|
|
|
const packageDefinition = protoLoader.loadSync('rpc.proto', loaderOptions);
|
2018-04-18 03:42:55 +02:00
|
|
|
|
|
|
|
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
|
|
|
|
|
2018-08-22 22:11:20 +02:00
|
|
|
// 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
|
2020-05-04 09:59:59 +02:00
|
|
|
let m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
|
|
|
|
let macaroon = m.toString('hex');
|
2018-04-18 03:42:55 +02:00
|
|
|
|
|
|
|
// build meta data credentials
|
2020-05-04 09:59:59 +02:00
|
|
|
let metadata = new grpc.Metadata()
|
2018-04-25 22:38:39 +02:00
|
|
|
metadata.add('macaroon', macaroon)
|
2020-05-04 09:59:59 +02:00
|
|
|
let macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => {
|
2018-04-18 03:42:55 +02:00
|
|
|
callback(null, metadata);
|
|
|
|
});
|
|
|
|
|
|
|
|
// build ssl credentials using the cert the same as before
|
2020-05-04 09:59:59 +02:00
|
|
|
let lndCert = fs.readFileSync("~/.lnd/tls.cert");
|
|
|
|
let sslCreds = grpc.credentials.createSsl(lndCert);
|
2018-04-18 03:42:55 +02:00
|
|
|
|
|
|
|
// combine the cert credentials and the macaroon auth credentials
|
|
|
|
// such that every call is properly encrypted and authenticated
|
2020-05-04 09:59:59 +02:00
|
|
|
let credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
|
2018-04-18 03:42:55 +02:00
|
|
|
|
|
|
|
// Pass the crendentials when creating a channel
|
2020-05-04 09:57:18 +02:00
|
|
|
let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
2020-05-04 09:59:59 +02:00
|
|
|
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);
|
|
|
|
});
|
2018-04-18 03:42:55 +02:00
|
|
|
```
|
|
|
|
|
2020-05-04 09:56:12 +02:00
|
|
|
## Conclusion
|
2017-08-04 01:18:15 +02:00
|
|
|
|
2017-03-01 01:22:02 +01:00
|
|
|
With the above, you should have all the `lnd` related `gRPC` dependencies
|
2017-08-04 01:18:15 +02:00
|
|
|
installed locally in your project. In order to get up to speed with `protofbuf`
|
|
|
|
usage from Javascript, see [this official `protobuf` reference for
|
2017-03-01 01:22:02 +01:00
|
|
|
Javascript](https://developers.google.com/protocol-buffers/docs/reference/javascript-generated).
|
|
|
|
Additionally, [this official gRPC
|
2017-08-04 01:18:15 +02:00
|
|
|
resource](http://www.grpc.io/docs/tutorials/basic/node.html) provides more
|
|
|
|
details around how to drive `gRPC` from `node.js`.
|