├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example.js ├── index.js ├── methods.json ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # IDEA Project file 61 | *.iml 62 | .idea/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017-2017 Alekos Filini (BHB Network) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING 2 | 3 | This software is for testing and demonstration purposes only. This software and c-lightning are still very new and contain known and unknown bugs. In particular, be warned: YOU MAY LOSE FUNDS! 4 | 5 | # lightning-client-js 6 | 7 | JavaScript [c-lightning](https://github.com/ElementsProject/lightning) client. 8 | 9 | This repository is published as the [`lightning-client`](https://www.npmjs.com/package/lightning-client) NPM module. 10 | 11 | ## Installing the client 12 | 13 | You can easily install this client using `npm` by running: 14 | 15 | ``` 16 | npm install lightning-client 17 | ``` 18 | 19 | ## Using the client 20 | 21 | Once the client is installed you can use it by loading the main class and instantiating it in this way: 22 | 23 | ```javascript 24 | 25 | 'use strict'; 26 | 27 | const LightningClient = require('lightning-client'); 28 | 29 | // This should point to your lightning-dir, by default in ~/.lightning. 30 | // The debug mode is enabled (second parameter) but this decreases performances (see PR #10) 31 | const client = new LightningClient('/home/bitcoind/.lightning', true); 32 | 33 | // Every call returns a Promise 34 | 35 | // "Show information about this node" 36 | client.getinfo() 37 | .then(info => console.log(info)) 38 | .catch(err => console.log(err)); 39 | 40 | // "Create an invoice for {msatoshi} with {label} and {description} with optional {expiry} seconds (default 1 hour)" } 41 | client.invoice(100, 'my-label-4', 'my-description-4', 3600) 42 | .then(result => console.log(result)) 43 | .catch(err => console.log(err)); 44 | 45 | // "Show addresses list up to derivation {index} (default is the last bip32 index)" 46 | client.devListaddrs () 47 | .then(listaddrs => console.log(JSON.stringify (listaddrs))) 48 | .catch(err => console.log(err)); 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const LightningClient = require('lightning-client'); 4 | // const LightningClient = require('./index'); 5 | 6 | // This should point to your lightning-dir, by default in ~/.lightning. 7 | // The debug mode is enabled (second parameter) but this decreases performances (see PR #10) 8 | const client = new LightningClient('/home/bitcoind/.lightning', true); 9 | 10 | // Every call returns a Promise 11 | 12 | // "Show information about this node" 13 | client.getinfo() 14 | .then(info => console.log(info)) 15 | .catch(err => console.log(err)); 16 | 17 | // "Create an invoice for {msatoshi} with {label} and {description} with optional {expiry} seconds (default 1 hour)" } 18 | client.invoice(100, 'my-label-4', 'my-description-4', 3600) 19 | .then(result => console.log(result)) 20 | .catch(err => console.log(err)); 21 | 22 | // "Show addresses list up to derivation {index} (default is the last bip32 index)" 23 | client.devListaddrs () 24 | .then(listaddrs => console.log(JSON.stringify (listaddrs))) 25 | .catch(err => console.log(err)); 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const net = require('net'); 5 | const debug = require('debug')('lightning-client'); 6 | const {EventEmitter} = require('events'); 7 | const _ = require('lodash'); 8 | const methods = require('./methods'); 9 | 10 | class LightningClient extends EventEmitter { 11 | constructor(rpcPath, debugFlag = false) { 12 | if (!path.isAbsolute(rpcPath)) { 13 | throw new Error('The rpcPath must be an absolute path'); 14 | } 15 | 16 | rpcPath = path.join(rpcPath, '/lightning-rpc'); 17 | 18 | debug(`Connecting to ${rpcPath}`); 19 | 20 | super(); 21 | this.rpcPath = rpcPath; 22 | this.reconnectWait = 0.5; 23 | this.reconnectTimeout = null; 24 | this.reqcount = 0; 25 | 26 | this.debug = debugFlag; 27 | 28 | const _self = this; 29 | 30 | this.client = net.createConnection(rpcPath); 31 | this.clientConnectionPromise = new Promise(resolve => { 32 | _self.client.on('connect', () => { 33 | debug(`Lightning client connected`); 34 | _self.reconnectWait = 1; 35 | resolve(); 36 | }); 37 | 38 | _self.client.on('end', () => { 39 | console.error('Lightning client connection closed, reconnecting'); 40 | _self.increaseWaitTime(); 41 | _self.reconnect(); 42 | }); 43 | 44 | _self.client.on('error', error => { 45 | console.error(`Lightning client connection error`, error); 46 | _self.increaseWaitTime(); 47 | _self.reconnect(); 48 | }); 49 | }); 50 | 51 | let buffer = Buffer.from(''); 52 | let openCount = 0; 53 | 54 | this.client.on('data', data => { 55 | _.each(LightningClient.splitJSON(Buffer.concat([buffer, data]), buffer.length, openCount), partObj => { 56 | if (partObj.partial) { 57 | buffer = partObj.string; 58 | openCount = partObj.openCount; 59 | 60 | return; 61 | } 62 | 63 | buffer = Buffer.from(''); 64 | openCount = 0; 65 | 66 | try { 67 | let dataObject = JSON.parse(partObj.string.toString()); 68 | _self.emit('res:' + dataObject.id, dataObject); 69 | } catch (err) { 70 | return; 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | static splitJSON(str, startFrom = 0, openCount = 0) { 77 | const parts = []; 78 | 79 | let lastSplit = 0; 80 | 81 | for (let i = startFrom; i < str.length; i++) { 82 | if (i > 0 && str[i - 1] === 115) { // 115 => backslash, ignore this character 83 | continue; 84 | } 85 | 86 | if (str[i] === 123) { // '{' 87 | openCount++; 88 | } else if (str[i] === 125) { // '}' 89 | openCount--; 90 | 91 | if (openCount === 0) { 92 | const start = lastSplit; 93 | const end = (i + 1 === str.length) ? undefined : i + 1; 94 | 95 | parts.push({partial: false, string: str.slice(start, end), openCount: 0}); 96 | 97 | lastSplit = end; 98 | } 99 | } 100 | } 101 | 102 | if (lastSplit !== undefined) { 103 | parts.push({partial: true, string: str.slice(lastSplit), openCount}); 104 | } 105 | 106 | return parts; 107 | } 108 | 109 | increaseWaitTime() { 110 | if (this.reconnectWait >= 16) { 111 | this.reconnectWait = 16; 112 | } else { 113 | this.reconnectWait *= 2; 114 | } 115 | } 116 | 117 | reconnect() { 118 | const _self = this; 119 | 120 | if (this.reconnectTimeout) { 121 | return; 122 | } 123 | 124 | this.reconnectTimeout = setTimeout(() => { 125 | debug('Trying to reconnect...'); 126 | 127 | _self.client.connect(_self.rpcPath); 128 | _self.reconnectTimeout = null; 129 | }, this.reconnectWait * 1000); 130 | } 131 | 132 | call(method, args = []) { 133 | if (!_.isString(method) || !_.isArray(args)) { 134 | return Promise.reject(new Error('invalid_call')); 135 | } 136 | 137 | let stackTrace = null; 138 | if (this.debug === true) { // not really efficient, we skip this step if debug is not enabled 139 | const error = new Error(); 140 | stackTrace = error.stack; 141 | } 142 | 143 | const _self = this; 144 | 145 | const callInt = ++this.reqcount; 146 | const sendObj = { 147 | method, 148 | params: args, 149 | id: callInt.toString() 150 | }; 151 | 152 | // Wait for the client to connect 153 | return this.clientConnectionPromise 154 | .then(() => new Promise((resolve, reject) => { 155 | // Wait for a response 156 | this.once('res:' + callInt, response => { 157 | if (_.isNil(response.error)) { 158 | resolve(response.result); 159 | return; 160 | } 161 | 162 | reject({error: response.error, stack: stackTrace}); 163 | }); 164 | 165 | // Send the command 166 | _self.client.write(JSON.stringify(sendObj)); 167 | })); 168 | } 169 | } 170 | 171 | const protify = s => s.replace(/-([a-z])/g, m => m[1].toUpperCase()); 172 | 173 | methods.forEach(k => { 174 | LightningClient.prototype[protify(k)] = function (...args) { 175 | return this.call(k, args); 176 | }; 177 | }); 178 | 179 | module.exports = LightningClient; 180 | -------------------------------------------------------------------------------- /methods.json: -------------------------------------------------------------------------------- 1 | [ 2 | "dev-blockheight", 3 | "dev-setfees", 4 | "listnodes", 5 | "getroute", 6 | "listchannels", 7 | "invoice", 8 | "listinvoices", 9 | "delinvoice", 10 | "waitanyinvoice", 11 | "waitinvoice", 12 | "decodepay", 13 | "help", 14 | "stop", 15 | "getlog", 16 | "dev-rhash", 17 | "dev-crash", 18 | "getinfo", 19 | "listconfigs", 20 | "sendpay", 21 | "pay", 22 | "listpayments", 23 | "connect", 24 | "listpeers", 25 | "fundchannel", 26 | "close", 27 | "dev-sign-last-tx", 28 | "dev-fail", 29 | "dev-reenable-commit", 30 | "dev-ping", 31 | "dev-memdump", 32 | "dev-memleak", 33 | "withdraw", 34 | "newaddr", 35 | "dev-listaddrs", 36 | "listfunds", 37 | "dev-rescan-outputs" 38 | ] 39 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightning-client", 3 | "version": "0.5.2-beta.7", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "debug": { 8 | "version": "3.1.0", 9 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 10 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 11 | "requires": { 12 | "ms": "2.0.0" 13 | } 14 | }, 15 | "lodash": { 16 | "version": "4.17.4", 17 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 18 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 19 | }, 20 | "ms": { 21 | "version": "2.0.0", 22 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 23 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightning-client", 3 | "version": "0.5.2-beta.8", 4 | "description": "JavaScript c-lightning client", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "xo" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/BHBNETWORK/lightning-client-js.git" 12 | }, 13 | "keywords": [ 14 | "lightning", 15 | "network", 16 | "bitcoin" 17 | ], 18 | "author": "Alekos Filini", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/BHBNETWORK/lightning-client-js/issues" 22 | }, 23 | "homepage": "https://github.com/BHBNETWORK/lightning-client-js#readme", 24 | "xo": { 25 | "esnext": true, 26 | "space": 4, 27 | "rules": {}, 28 | "envs": [ 29 | "node" 30 | ] 31 | }, 32 | "dependencies": { 33 | "debug": "^3.1.0", 34 | "lodash": "^4.17.4" 35 | } 36 | } 37 | --------------------------------------------------------------------------------