lnmessage
Talk to Lightning nodes from the Browser and NodeJS apps.
Features
- Connect to a lightning node via a WebSocket or TCP Socket connection.
- Works in the Browser and Node without any polyfilling.
- Initialise with a session secret to have a persistent node public key for the browser.
- Control a Core Lightning node via Commando RPC calls.
- Automatic handling of ping messages to ensure constant connection to the node.
- Automatic decryption of all incoming messages. You can subscribe to a stream of decrypted messages and do whatever you like with them. The idea is that this library will eventually send and handle more than just init, ping and commando messages. In the mean time it can be extended pretty easily to handle any kind of Lightning messages.
- Automatic WebSocket re-connection handling.
Installation
Yarn
yarn add lnmessage
NPM
npm i lnmessage
Quickstart
import Lnmessage from 'lnmessage'
async function connect() {
const ln = new Lnmessage({
remoteNodePublicKey: '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f',
wsProxy: 'wss://<WEBSOCKET_PROXY>',
tcpSocket: new net.Socket(),
ip: '35.232.170.67',
port: 9735,
privateKey: 'd6a2eba36168cc31e97396a781a4dd46dd3648c001d3f4fde221d256e41715ea'
})
await ln.connect()
ln.commando({
method: 'getinfo',
params: [],
rune: '<BASE64_RUNE>'
})
}
connect()
Initialisation
type LnWebSocketOptions = {
remoteNodePublicKey: string
ip: string
port?: number
wsProxy?: string
wsProtocol?: 'ws:' | 'wss:'
tcpSocket?: TCPSocket
privateKey?: string
logger?: Logger
}
const options: LnWebSocketOptions = {
remoteNodePublicKey: '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f',
wsProxy: 'wss://<WEBSOCKET_PROXY>',
ip: '35.232.170.67',
port: 9735,
privateKey: 'd6a2eba36168cc31e97396a781a4dd46dd3648c001d3f4fde221d256e41715ea',
logger: {
info: console.log,
warn: console.warn,
error: console.error
}
}
const ln = new Lnmessage(options)
Connecting
const connected = await ln.connect()
if (connected) {
console.log('Connected and ready to send/receive messages')
}
Commando RPC Requests
If you are connecting to a Core Ln node and you have a valid rune authentication token, you can use Lnmessage to securely call the node RPC server using the commando
method.
type CommandoRequest = {
method: string
params?: unknown | unknown[]
rune: string
reqId?: string
}
const getInfoRequest: CommandoRequest = {
method: 'getinfo',
rune: '7jN2zKjkWlvncm_La3uZc9vLVGLu7xl9oBoun6pth7E9MA=='
}
const getInfoReponse = await ln.commando(getInfoRequest)
function toHexString(byteArray: Uint8Array) {
return byteArray.reduce((output, elem) => output + ('0' + elem.toString(16)).slice(-2), '')
}
function createRandomHex(length = 32) {
const bytes = new Uint8Array(length)
return toHexString(crypto.getRandomValues(bytes))
}
const reqId = await createRandomHex(8)
const waitInvoiceRequest: CommandoRequest = {
method: 'waitanyinvoice',
params: { lastpay_index: lastPayIndex },
rune: '7jN2zKjkWlvncm_La3uZc9vLVGLu7xl9oBoun6pth7E9MA==',
reqId
}
const invoiceUpdate = await ln.commando(waitInvoiceRequest)
API
RxJs Observables are used throughout the API and are indicated by a $
at the end of the variable name. You do not need to use or understand RxJs to make use of these variables. Simply call the subscribe
method on these variable and pass in a call back for all updates and then call the unsubscribe
method on the returned object when you no longer want to receive updates.
class Lnmessage {
public noise: NoiseState
public remoteNodePublicKey: string
public publicKey: string
public privateKey: string
public wsUrl: string
public socket: WebSocket | null
public connectionStatus$: BehaviorSubject<ConnectionStatus>
public decryptedMsgs$: Observable<Buffer>
public commandoMsgs$: Observable<
(JsonRpcSuccessResponse | JsonRpcErrorResponse) & { reqId: string }
>
public Buffer: BufferConstructor
public connect(attemptReconnect = true): Promise<boolean>
public disconnect(): void
public commando(request: CommandoRequest): Promise<JsonRpcSuccessResponse['result']>
}
WebSocket Proxy
There are some limitations to connecting to Lightning nodes within a browser. Core Lightning nodes can be directly connected to if the experimental-websocket-port
option is set in the config. This will allow a direct connection to the node, but if you are running a browser app on https, then it will not allow a connection to a non SSL WebSocket endpoint, so you would need to setup SSL for your node. As far as I know LND nodes do not accept connections via WebSocket at this time.
So to simplify connecting to any Lightning node, you can go through a WebSocket proxy (see Clams and jb55's WebSocket proxy server repos). Going through a proxy like this requires no trust in the server. The WebSocket connection is initated with the proxy, which then creates a regular TCP socket connection to the node. Then all messages are fully encrypted via the noise protocol, so the server only sees encrypted binary traffic that is simply proxied between the browser and the node. Currently only clearnet is supported, but I believe that the WebSocket proxy code could be modified to create a socket connection to a TOR only node to make this work.
TOR
Connecting to a node over TOR requires the proxy server to support it.
Running Locally
Install Deps
Yarn - yarn
NPM - npm i
Build
Yarn - yarn build
NPM - npm run build
Acknowledgments
- This library was inspired by jb55's lnsocket library. Lnsocket is awesome, but I wanted something that was a bit more browser friendly and with a few more features.
- Most of the code comes from the Node Lightning project and has been modified to be browser friendly.
- The chacha encryption algorithm is a modified version of the chacha library.