balanceofsatoshis
Version:
Lightning balance CLI
149 lines (121 loc) • 4.38 kB
JavaScript
const asyncAuto = require('async/auto');
const asyncMap = require('async/map');
const {getChannel} = require('ln-service');
const {getChannels} = require('ln-service');
const {getNode} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const tokensAsMtokens = tokens => BigInt(tokens) * BigInt(1e3);
/** Get inbound path
{
destination: <Final Destination Public Key Hex String>
identity: <Node Identity Public Key Hex String>
lnd: <Authenticated LND API Object>
through: <In Through Node with Public Key Hex String>
tokens: <Tokens to Send Number>
}
@returns via cbk or Promise
{
path: [{
[base_fee_mtokens]: <Base Fee Millitokens String>
[channel]: <Standard Format Channel Id String>
[channel_capacity]: <Channel Capacity Tokens Number>
[cltv_delta]: <Channel CLTV Delta Number>
[fee_rate]: <Proportional Fee Rate Number>
public_key: <Destination Public Key Hex String>
}]
}
*/
module.exports = ({destination, identity, lnd, through, tokens}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!destination) {
return cbk([400, 'ExpectedDestinationToGetInboundPath']);
}
if (!identity) {
return cbk([400, 'ExpectedIdentityToGetInboundPath']);
}
if (!lnd) {
return cbk([400, 'ExpectedLndToGetInboundPath']);
}
if (!through) {
return cbk([400, 'ExpectedInThroughPublicKeyHexString']);
}
if (tokens === undefined) {
return cbk([400, 'ExpectedTokensToGetInboundPath']);
}
return cbk();
},
// Get channels to validate the inbound channel exists
getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
// Get node
getNode: ['validate', ({}, cbk) => {
return getNode({lnd, public_key: destination}, cbk);
}],
// Local channels
localChannels: ['getChannels', ({getChannels}, cbk) => {
const localChannels = getChannels.channels.filter(n => {
return n.partner_public_key === through && !!n.is_private;
});
return asyncMap(localChannels, (channel, cbk) => {
return getChannel({lnd, id: channel.id}, cbk);
},
cbk);
}],
// Connecting path
path: [
'getChannels',
'getNode',
'localChannels',
({getChannels, getNode, localChannels}, cbk) =>
{
const channels = [].concat(getNode.channels).concat(localChannels);
const connectingChannels = channels.filter(chan => {
// Channel has a policy that matches the key of the through key
return !!chan.policies.find(n => n.public_key === through);
});
if (!connectingChannels.length) {
return cbk([400, 'NoConnectingChannelToPayIn']);
}
const [channel] = connectingChannels.filter(chan => {
const policy = chan.policies.find(n => n.public_key === through);
if (!!chan.capacity && chan.capacity < tokens) {
return false;
}
if (!policy.max_htlc_mtokens) {
return false;
}
const isLocal = chan.policies.find(n => n.public_key === identity);
const localChannel = getChannels.channels.find(({id}) => {
return id === chan.id;
});
// Exit early when this is a local channel but doesn't exist
if (!!isLocal && !localChannel) {
return false;
}
return BigInt(policy.max_htlc_mtokens) > tokensAsMtokens(tokens);
});
if (!channel) {
return cbk([400, 'NoSufficientCapacityConnectingChannelToPayIn']);
}
const policy = channel.policies.find(n => n.public_key === through);
const path = [
{
public_key: through,
},
{
base_fee_mtokens: policy.base_fee_mtokens,
channel: channel.id,
channel_capacity: channel.capacity,
cltv_delta: policy.cltv_delta,
fee_rate: policy.fee_rate,
public_key: destination,
},
];
return cbk(null, {path});
}],
},
returnResult({reject, resolve, of: 'path'}, cbk));
});
};