balanceofsatoshis
Version: 
Lightning balance CLI
104 lines (81 loc) • 2.88 kB
JavaScript
const flatten = arr => [].concat(...arr);
const {isArray} = Array;
const uniq = arr => Array.from(new Set(arr));
/** Calculate multi-probe adjustments
  Treat all hops except the first as already used, so as not to overlap on
  subsequent probes.
  Every probe success on a peer will draw down budgets of their channels.
  The caller can check on these budgets to decide if it's time to move on to
  another public key, due to lack of budget.
  {
    channels: [{
      id: <Standard Format Channel Id String>
      local_balance: <Local Balance Tokens Number>
      local_reserve: <Local Reserve Tokens Number>
      partner_public_key: <Public Key Hex String>
    }]
    from: <From Public Key Hex String>
    ignore: [{
      from_public_key: <Public Key Hex String>
      [to_public_key]: <To Public Key Hex String>
    }]
    probes: [{
      latency_ms: <Latency Milliseconds Number>
      relays: [<Public Key Hex String>]
      route_maximum: <Route Maximum Number>
    }]
    routes: [{
      public_key: <Hop Public Key Hex String>
    }]
    tokens: <Starting Tokens Number>
  }
  @throws
  <Error>
  @returns
  {
    ignore: [{
      from_public_key: <Public Key Hex String>
      [to_public_key]: <To Public Key Hex String>
    }]
  }
*/
module.exports = ({channels, from, ignore, probes, tokens}) => {
  if (!isArray(probes)) {
    throw new Error('ExpectedArrayOfProbesToGenerateMultiProbeIgnores');
  }
  if (probes.filter(n => !!n).length !== probes.length) {
    throw new Error('ExpectedProbeDetailsToGenerateMultiProbeIgnores');
  }
  if (!!probes.find(n => !isArray(n.relays))) {
    throw new Error('ExpectedArrayOfRelaysToGenerateMultiProbeIgnores');
  }
  const latencies = probes.map(n => n.latency_ms).filter(n => !!n);
  const msSpent = latencies.reduce((sum, n) => sum + n, Number());
  const pairs = probes.map(probe => {
    const [out, ...network] = probe.relays.map((to, i, arr) => {
      return {from_public_key: arr[--i] || from, to_public_key: to};
    });
    return {network, out: out.to_public_key, used: probe.route_maximum};
  });
  const outPeers = uniq(pairs.map(n => n.out));
  const exhausted = outPeers.filter(out => {
    const used = pairs.filter(n => n.out === out).map(n => n.used);
    const available = channels
      .filter(n => n.partner_public_key === out)
      .map(n => n.local_balance - n.local_reserve)
      .filter(n => n > tokens);
    used.forEach(amount => {
      const channel = available.findIndex(n => n > amount);
      return available[channel] -= amount;
    });
    return !available.find(n => n > tokens);
  });
  const exhaustedIgnores = exhausted.map(to => ({
    from_public_key: from,
    to_public_key: to,
  }));
  const networkIgnores = flatten(pairs.map(n => n.network));
  return {
    ignore: [].concat(ignore).concat(exhaustedIgnores).concat(networkIgnores),
  };
};