balanceofsatoshis
Version:
Lightning balance CLI
144 lines (116 loc) • 4.11 kB
JavaScript
const {decodeChanId} = require('bolt07');
const flatten = arr => [].concat(...arr);
const {shuffle} = require('./../arrays');
const {isMatchingFilters} = require('./../display');
const {max} = Math;
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
const tok = n => Number(BigInt(n) / BigInt(1e3));
/** Find a node for a tag query
{
channels: [{
id: <Standard Format Channel Id String>
local_balance: <Channel Local Balance Tokens Number>
partner_public_key: <Peer Public Key Hex String>
remote_balance: <Channel Local Balance Tokens Number>
}]
[filters]: [<Filter Expression String>]
policies: {
[base_fee_mtokens]: <Remote Base Fee Charged In Millitokens Number>
[fee_rate]: <Remote Fees Charged in Millitokens Per Million Number>
[is_disabled]: <Remote Channel Forwarding Is Disabled Bool>
public_key: <Remote Public Key Hex String>
}
query: <Query String>
tags: [{
[alias]: <Tag Alias String>
id: <Tag Id Hex String>
[nodes]: [<Public Key Hex String>]
}]
}
@returns
{
[failure]: {
error: <Error String>
formula: <Errored Formula String>
}
[match]: <Matching Node Public Key Hex String>
[matches]: [{
[alias]: <Tag Alias String>
id: <Tag Id Hex String>
[nodes]: [<Public Key Hex String>]
}]
}
*/
module.exports = ({channels, filters, policies, tags, query}) => {
const disabled = policies.filter(n => n.is_disabled).map(n => n.public_key);
const peerKeys = channels
.map(n => n.partner_public_key)
.filter(n => !disabled.includes(n));
// Find tags that match on id or on alias, and also have relevant nodes
const matches = tags.filter(tag => {
const nodes = (tag.nodes || []).filter(n => peerKeys.includes(n));
if (!nodes.length) {
return false;
}
const alias = tag.alias || String();
const isAliasMatch = alias.toLowerCase() === (query || '').toLowerCase();
const isIdMatch = tag.id.startsWith(query);
return isAliasMatch || isIdMatch;
});
const [tagMatch, ...otherTagMatches] = matches;
// Exit early when there are no matches at all
if (!tagMatch) {
return {};
}
// Exit early when there is ambiguity around the matching
if (!!otherTagMatches.length) {
return {matches};
}
// Get the array of nodes in the tag match
const tagMatches = tagMatch.nodes.filter(n => peerKeys.includes(n));
// Filter out matches in the array of peers that do not fulfill criteria
const array = tagMatches
.map(key => {
if (!filters || !filters.length) {
return {match: key};
}
const peerPolicies = policies.filter(n => n.public_key === key);
const withPeer = channels.filter(n => n.partner_public_key === key);
const feeRates = peerPolicies.filter(n => n.fee_rate !== undefined);
const pendingPayments = withPeer.map(n => n.pending_payments.length);
const matching = isMatchingFilters({
filters: filters || [],
variables: {
capacity: sumOf(withPeer.map(n => n.capacity)),
heights: withPeer.map(n => {
return decodeChanId({channel: n.id}).block_height;
}),
inbound_base_fee: max(...feeRates.map(n => tok(n.base_fee_mtokens))),
inbound_fee_rate: max(...feeRates.map(n => n.fee_rate)),
inbound_liquidity: sumOf(withPeer.map(n => n.remote_balance)),
outbound_liquidity: sumOf(withPeer.map(n => n.local_balance)),
pending_payments: sumOf(pendingPayments),
},
});
if (!!matching.failure) {
return matching;
}
if (!matching.is_matching) {
return;
}
return {match: key};
})
.filter(n => !!n);
// Exit early when there is no match
if (!array.length) {
return {};
}
// Exit early when there is a failure in the tag
if (!!array.find(n => !!n.failure)) {
return array.find(n => !!n.failure);
}
// Shuffle the results
const {shuffled} = shuffle({array});
const [{match}] = shuffled;
return {match};
};