lightning
Version:
Lightning Network client library
216 lines (170 loc) • 6.45 kB
JavaScript
const asyncAuto = require('async/auto');
const {chanNumber} = require('bolt07');
const {returnResult} = require('asyncjs-util');
const {addPeer} = require('./../peers');
const {getChannel} = require('./../info');
const {isLnd} = require('./../../lnd_requests');
const defaultConfTarget = 6;
const method = 'closeChannel';
const type = 'default';
/** Close a channel.
Either an id or a transaction id / transaction output index is required
If cooperatively closing, pass a public key and socket to connect
`max_tokens_per_vbyte` will be ignored when closing a peer initiated channel
Requires `info:read`, `offchain:write`, `onchain:write`, `peers:write`
permissions
`max_tokens_per_vbyte` is not supported in LND 0.15.0 and below
`is_graceful_close` is not supported in LND 0.17.5 and below
{
[address]: <Request Sending Local Channel Funds To Address String>
[id]: <Standard Format Channel Id String>
[is_force_close]: <Is Force Close Bool>
[is_graceful_close]: <Is Waiting For Pending Payments to Coop Close Bool>
lnd: <Authenticated LND API Object>
[max_tokens_per_vbyte]: <Fail Cooperative Close Above Fee Rate Number>
[public_key]: <Peer Public Key String>
[socket]: <Peer Socket String>
[target_confirmations]: <Confirmation Target Number>
[tokens_per_vbyte]: <Target Tokens Per Virtual Byte Number>
[transaction_id]: <Transaction Id Hex String>
[transaction_vout]: <Transaction Output Index Number>
}
@returns via cbk or Promise
{
transaction_id: <Closing Transaction Id Hex String>
transaction_vout: <Closing Transaction Vout Number>
}
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
const targetConfs = args.target_confirmations;
const txId = args.transaction_id;
const vout = args.transaction_vout;
const isDirectClose = !!txId && vout !== undefined;
if (!!args.address && !!args.is_force_close) {
return cbk([400, 'ExpectedCoopCloseWhenCloseAddressSpecified']);
}
if (!args.id && !isDirectClose) {
return cbk([400, 'ExpectedIdOfChannelToClose']);
}
if (!isLnd({method, type, lnd: args.lnd})) {
return cbk([400, 'ExpectedLndToExecuteChannelClose']);
}
if (!!args.public_key !== !!args.socket) {
return cbk([400, 'ExpectedBothPublicKeyAndSocketForChannelClose']);
}
if (!!targetConfs && args.tokens_per_vbyte !== undefined) {
return cbk([400, 'UnexpectedTokensPerVbyteForChannelClose']);
}
if (!!args.is_force_close && args.tokens_per_vbyte !== undefined) {
return cbk([400, 'UnexpectedFeeSettingForForceCloseChannel']);
}
return cbk();
},
// Add peer
addPeer: ['validate', ({}, cbk) => {
// Exit early and avoid adding peer when force close, unknown peer key
if (!!args.is_force_close || !args.public_key) {
return cbk();
}
return addPeer({
lnd: args.lnd,
public_key: args.public_key,
socket: args.socket,
},
cbk);
}],
// Get a single channel
getChannel: ['validate', ({}, cbk) => {
if (!args.id) {
return cbk(null, {
transaction_id: args.transaction_id,
transaction_vout: args.transaction_vout,
});
}
return getChannel({id: args.id, lnd: args.lnd}, cbk);
}],
// Determine what the confirmations to confirm should be
targetConf: ['validate', ({}, cbk) => {
// Exit early when a chain fee cannot be specified
if (!!args.is_force_close) {
return cbk();
}
// Exit early when there is a chain fee rate specified
if (!!args.tokens_per_vbyte) {
return cbk();
}
return cbk(null, args.target_confirmations || defaultConfTarget);
}],
// Close out the channel
closeChannel: [
'addPeer',
'getChannel',
'targetConf',
({getChannel, targetConf}, cbk) =>
{
let isFinished = false;
const tokensPerVByte = args.tokens_per_vbyte;
const transactionId = Buffer.from(getChannel.transaction_id, 'hex');
const transactionVout = getChannel.transaction_vout;
const closeChannel = args.lnd[type][method]({
channel_point: {
funding_txid_bytes: transactionId.reverse(),
output_index: transactionVout,
},
delivery_address: args.address || undefined,
force: !!args.is_force_close,
max_fee_per_vbyte: args.max_tokens_per_vbyte || undefined,
no_wait: args.is_graceful_close || undefined,
sat_per_vbyte: !!tokensPerVByte ? tokensPerVByte : undefined,
target_conf: targetConf,
});
const finished = (err, res) => {
if (!!isFinished) {
return;
}
closeChannel.cancel();
isFinished = true;
return cbk(err, res);
};
closeChannel.on('data', chan => {
switch (chan.update) {
case 'chan_close':
break;
case 'close_pending':
if (!chan.close_pending) {
return finished([503, 'ExpectedClosePendingData']);
}
if (chan.close_pending.output_index === undefined) {
return finished([503, 'ExpectedOutputIndexForPendingChanClose']);
}
if (!chan.close_pending.txid) {
return finished([503, 'ExpectedClosePendingTransactionId']);
}
const closeTxId = chan.close_pending.txid.reverse();
finished(null, {
transaction_id: closeTxId.toString('hex'),
transaction_vout: chan.close_pending.output_index,
});
break;
case 'confirmation':
break;
default:
break;
}
return;
});
closeChannel.on('end', () => {});
closeChannel.on('error', err => {
return finished([503, 'UnexpectedCloseChannelError', {err}]);
});
closeChannel.on('status', () => {});
return;
}],
},
returnResult({reject, resolve, of: 'closeChannel'}, cbk));
});
};