lightning
Version:
Lightning Network client library
240 lines (204 loc) • 9.36 kB
JavaScript
const asyncAuto = require('async/auto');
const asyncMapSeries = require('async/mapSeries');
const {chanFormat} = require('bolt07');
const {returnResult} = require('asyncjs-util');
const {isLnd} = require('./../../lnd_requests');
const {rpcResolutionAsResolution} = require('./../../lnd_responses');
const emptyTxId = Buffer.alloc(32).toString('hex');;
const {isArray} = Array;
const method = 'closedChannels';
const outpointSeparator = ':';
/** Get closed out channels
Multiple close type flags are supported.
Requires `offchain:read` permission
`other_ids is not supported on LND 0.15.0 and below
{
[is_breach_close]: <Only Return Breach Close Channels Bool>
[is_cooperative_close]: <Only Return Cooperative Close Channels Bool>
[is_funding_cancel]: <Only Return Funding Canceled Channels Bool>
[is_local_force_close]: <Only Return Local Force Close Channels Bool>
[is_remote_force_close]: <Only Return Remote Force Close Channels Bool>
lnd: <Authenticated LND API Object>
}
@returns via cbk or Promise
{
channels: [{
[anchor_is_confirmed]: <Anchor Sweep Confirmed Bool>
[anchor_is_pending]: <Anchor Sweep Pending Confirmation Bool>
[anchor_spent_by]: <Anchor Sweep Transaction Id Hex String>
[anchor_vout]: <Anchor Output Index Number>
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>
}]
}
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!isLnd({method, lnd: args.lnd, type: 'default'})) {
return cbk([400, 'ExpectedLndApiForGetClosedChannelsRequest'])
}
return cbk();
},
// Get closed channels
getClosedChannels: ['validate', ({}, cbk) => {
return args.lnd.default[method]({
breach: args.is_breach_close || undefined,
cooperative: args.is_cooperative_close || undefined,
funding_canceled: args.is_breach_close || undefined,
local_force: args.is_local_force_close || undefined,
remote_force: args.is_remote_force_close || undefined,
},
(err, res) => {
if (!!err) {
return cbk([503, 'FailedToRetrieveClosedChannels', {err}]);
}
if (!res) {
return cbk([503, 'ExpectedResponseToGetCloseChannels']);
}
if (!isArray(res.channels)) {
return cbk([503, 'ExpectedChannels']);
}
return cbk(null, res.channels);
});
}],
// Map channels
mapChannels: ['getClosedChannels', ({getClosedChannels}, cbk) => {
return asyncMapSeries(getClosedChannels, (chan, cbk) => {
if (!isArray(chan.alias_scids)) {
return cbk([503, 'ExpectedArrayOfAliasShortChannelIds']);
}
if (!chan.capacity) {
return cbk([503, 'ExpectedCloseChannelCapacity']);
}
if (!chan.chan_id) {
return cbk([503, 'ExpectedChannelIdOfClosedChannel']);
}
if (!chan.channel_point) {
return cbk([503, 'ExpectedCloseChannelOutpoint']);
}
if (chan.close_height === undefined) {
return cbk([503, 'ExpectedChannelCloseHeight']);
}
if (!chan.closing_tx_hash) {
return cbk([503, 'ExpectedClosingTransactionId']);
}
if (!chan.remote_pubkey) {
return cbk([503, 'ExpectedCloseRemotePublicKey']);
}
if (!chan.settled_balance) {
return cbk([503, 'ExpectedFinalSettledBalance']);
}
if (!chan.time_locked_balance) {
return cbk([503, 'ExpectedFinalTimeLockedBalanceForClosedChan']);
}
const otherIds = chan.alias_scids
.filter(n => n !== chan.zero_conf_confirmed_scid)
.map(number => chanFormat({number}));
const closer = chan.close_initiator;
const finalTimeLock = Number(chan.time_locked_balance);
const hasCloseTx = chan.closing_tx_hash !== emptyTxId;
const hasId = chan.chan_id !== '0';
const height = !chan.close_height ? undefined : chan.close_height;
let isPartnerClosed;
let isPartnerInitiated;
const [txId, vout] = chan.channel_point.split(outpointSeparator);
const zeroConfId = chan.zero_conf_confirmed_scid;
const closeTxId = !hasCloseTx ? undefined : chan.closing_tx_hash;
const isLocalCooperativeClose = closer === 'INITIATOR_LOCAL';
const isRemoteCooperativeClose = closer === 'INITIATOR_REMOTE';
const number = !!Number(zeroConfId) ? zeroConfId : chan.chan_id;
const chanId = !hasId ? null : chanFormat({number});
// Try and determine if the channel was opened by our peer
if (chan.open_initiator === 'INITIATOR_LOCAL') {
isPartnerInitiated = false;
} else if (chan.open_initiator === 'INITIATOR_REMOTE') {
isPartnerInitiated = true;
}
// Try and determine if the channel was closed by our peer
if (chan.close_initiator === 'INITIATOR_LOCAL') {
isPartnerClosed = false;
} else if (chan.close_initiator === 'INITIATOR_REMOTE') {
isPartnerClosed = true;
}
if (chan.close_type === 'LOCAL_FORCE_CLOSE') {
isPartnerClosed = false
} else if (chan.close_type === 'REMOTE_FORCE_CLOSE') {
isPartnerClosed = true;
}
const chanResolutions = chan.resolutions || [];
try {
chanResolutions.map(rpcResolutionAsResolution);
} catch (err) {
return cbk([503, err.message]);
}
const resolutions = chanResolutions.map(rpcResolutionAsResolution);
const {anchor} = resolutions.find(n => n.anchor) || {anchor: {}};
const {balance} = resolutions.find(n => n.balance) || {balance: {}};
const payments = resolutions.map(n => n.payment).filter(n => !!n);
return cbk(null, {
anchor_is_confirmed: anchor.is_confirmed,
anchor_is_pending: anchor.is_pending,
anchor_spent_by: anchor.spent_by,
anchor_vout: anchor.transaction_vout,
capacity: Number(chan.capacity),
close_balance_spent_by: balance.spent_by,
close_balance_vout: balance.transaction_vout,
close_confirm_height: height,
close_payments: payments,
close_transaction_id: closeTxId,
final_local_balance: Number(chan.settled_balance),
final_time_locked_balance: finalTimeLock,
id: !chanId ? undefined : chanId.channel,
is_breach_close: chan.close_type === 'BREACH_CLOSE',
is_cooperative_close: chan.close_type === 'COOPERATIVE_CLOSE',
is_funding_cancel: chan.close_type === 'FUNDING_CANCELED',
is_local_force_close: chan.close_type === 'LOCAL_FORCE_CLOSE',
is_partner_closed: isPartnerClosed,
is_partner_initiated: isPartnerInitiated,
is_remote_force_close: chan.close_type === 'REMOTE_FORCE_CLOSE',
other_ids: otherIds.map(n => n.channel),
partner_public_key: chan.remote_pubkey,
transaction_id: txId,
transaction_vout: Number(vout),
});
},
cbk);
}],
// Closed channels
closedChannels: ['mapChannels', ({mapChannels}, cbk) => {
return cbk(null, {channels: mapChannels});
}],
},
returnResult({reject, resolve, of: 'closedChannels'}, cbk));
});
};