lightning
Version:
Lightning Network client library
230 lines (193 loc) • 7.77 kB
JavaScript
const EventEmitter = require('events');
const {isLnd} = require('./../../lnd_requests');
const {rpcChannelAsChannel} = require('./../../lnd_responses');
const {rpcClosedChannelAsClosed} = require('./../../lnd_responses');
const {rpcOutpointAsUpdate} = require('./../../lnd_responses');
const asError = msg => new Error(msg);
const emptyTxId = Buffer.alloc(32).toString('hex');
const eventActive = 'channel_active_changed';
const eventClosed = 'channel_closed';
const eventOpen = 'channel_opened';
const eventOpening = 'channel_opening';
const method = 'subscribeChannelEvents';
const shutDownMessage = 'Cancelled';
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
const type = 'default';
const updateActive = 'active_channel';
const updateInactive = 'inactive_channel';
const updateClosed = 'closed_channel';
const updateOpened = 'open_channel';
const updateOpening = 'pending_open_channel';
/** Subscribe to channel updates
Requires `offchain:read` permission
{
lnd: <Authenticated LND API Object>
}
`is_trusted_funding`, `other_ids` are not supported on LND 0.15.0 and below
`description` is not supported on LND 0.16.4 and below
@throws
<Error>
@returns
<EventEmitter Object>
@event 'channel_active_changed'
{
is_active: <Channel Is Active Bool>
transaction_id: <Channel Funding Transaction Id String>
transaction_vout: <Channel Funding Transaction Output Index Number>
}
@event 'channel_closed'
{
capacity: <Closed Channel Capacity Tokens Number>
[close_balance_spent_by]: <Channel Balance Output Spent By Tx Id String>
[close_balance_vout]: <Channel Balance Close Tx Output Index Number>
[close_confirm_height]: <Channel Close Confirmation Height Number>
close_payments: [{
is_outgoing: <Payment Is Outgoing Bool>
is_paid: <Payment Is Claimed With Preimage Bool>
is_pending: <Payment Resolution Is Pending Bool>
is_refunded: <Payment Timed Out And Went Back To Payer Bool>
[spent_by]: <Close Transaction Spent By Transaction Id Hex String>
tokens: <Associated Tokens Number>
transaction_id: <Transaction Id Hex String>
transaction_vout: <Transaction Output Index Number>
}]
[close_transaction_id]: <Closing Transaction Id Hex String>
final_local_balance: <Channel Close Final Local Balance Tokens Number>
final_time_locked_balance: <Closed Channel Timelocked Tokens Number>
[id]: <Closed Standard Format Channel Id String>
is_breach_close: <Is Breach Close Bool>
is_cooperative_close: <Is Cooperative Close Bool>
is_funding_cancel: <Is Funding Cancelled Close Bool>
is_local_force_close: <Is Local Force Close Bool>
is_partner_closed: <Channel Was Closed By Channel Peer Bool>
is_partner_initiated: <Channel Was Initiated By Channel Peer Bool>
is_remote_force_close: <Is Remote Force Close Bool>
other_ids: [<Other Channel Id String>]
partner_public_key: <Partner Public Key Hex String>
transaction_id: <Channel Funding Transaction Id Hex String>
transaction_vout: <Channel Funding Output Index Number>
}
@event 'channel_opened'
{
capacity: <Channel Token Capacity Number>
commit_transaction_fee: <Commit Transaction Fee Number>
commit_transaction_weight: <Commit Transaction Weight Number>
[cooperative_close_address]: <Coop Close Restricted to Address String>
[cooperative_close_delay_height]: <Prevent Coop Close Until Height Number>
[description]: <Channel Description String>
id: <Standard Format Channel Id String>
is_active: <Channel Active Bool>
is_closing: <Channel Is Closing Bool>
is_opening: <Channel Is Opening Bool>
is_partner_initiated: <Channel Partner Opened Channel Bool>
is_private: <Channel Is Private Bool>
[is_trusted_funding]: <Funding Output is Trusted Bool>
local_balance: <Local Balance Tokens Number>
local_given: <Local Initially Pushed Tokens Number>
local_reserve: <Local Reserved Tokens Number>
partner_public_key: <Channel Partner Public Key String>
past_states: <Total Count of Past Channel States Number>
pending_payments: [{
id: <Payment Preimage Hash Hex String>
is_outgoing: <Payment Is Outgoing Bool>
timeout: <Chain Height Expiration Number>
tokens: <Payment Tokens Number>
}]
received: <Received Tokens Number>
remote_balance: <Remote Balance Tokens Number>
remote_given: <Remote Initially Pushed Tokens Number>
remote_reserve: <Remote Reserved Tokens Number>
sent: <Sent Tokens Number>
transaction_id: <Blockchain Transaction Id String>
transaction_vout: <Blockchain Transaction Vout Number>
[type]: <Channel Commitment Transaction Type String>
unsettled_balance: <Unsettled Balance Tokens Number>
}
@event 'channel_opening'
{
transaction_id: <Blockchain Transaction Id Hex String>
transaction_vout: <Blockchain Transaction Output Index Number>
}
*/
module.exports = ({lnd}) => {
if (!isLnd({lnd, method, type})) {
throw new Error('ExpectedAuthenticatedLndToSubscribeToChannels');
}
const eventEmitter = new EventEmitter();
const subscription = lnd[type][method]({});
// Cancel the subscription when all listeners are removed
eventEmitter.on('removeListener', () => {
const events = [eventActive, eventClosed, eventOpen, eventOpening];
const listenerCounts = events.map(n => eventEmitter.listenerCount(n));
// Exit early when there are still listeners
if (!!sumOf(listenerCounts)) {
return;
}
subscription.cancel();
return;
});
const emitError = err => {
if (err.details === shutDownMessage) {
subscription.removeAllListeners();
} else {
subscription.cancel();
}
// Exit early when no one is listening to the error
if (!eventEmitter.listenerCount('error')) {
return;
}
return eventEmitter.emit('error', err);
};
subscription.on('data', update => {
if (!update) {
return emitError(asError('ExpectedEventDetailsInChannelSubscription'));
}
if (!update.type || !update.type.toLowerCase) {
return emitError(asError('ExpectedEventTypeInChannelSubscription'));
}
const updateType = update.type.toLowerCase();
const details = update[updateType];
if (!details) {
return emitError(asError('ExpectedEventDetailsForTypeInChannelSub'));
}
try {
switch (updateType) {
case updateActive:
eventEmitter.emit(eventActive, {
is_active: true,
transaction_id: rpcOutpointAsUpdate(details).transaction_id,
transaction_vout: rpcOutpointAsUpdate(details).transaction_vout,
});
break;
case updateClosed:
eventEmitter.emit(eventClosed, rpcClosedChannelAsClosed(details));
break;
case updateInactive:
eventEmitter.emit(eventActive, {
is_active: false,
transaction_id: rpcOutpointAsUpdate(details).transaction_id,
transaction_vout: rpcOutpointAsUpdate(details).transaction_vout,
});
break;
case updateOpened:
eventEmitter.emit(eventOpen, rpcChannelAsChannel(details));
break;
case updateOpening:
eventEmitter.emit(eventOpening, {
transaction_id: rpcOutpointAsUpdate(details).transaction_id,
transaction_vout: rpcOutpointAsUpdate(details).transaction_vout,
});
break;
default:
break;
}
} catch (err) {
return emitError(err);
}
return;
});
subscription.on('end', () => eventEmitter.emit('end'));
subscription.on('error', err => emitError(err));
subscription.on('status', status => eventEmitter.emit('status', status));
return eventEmitter;
};