UNPKG

ln-telegram

Version:
164 lines (135 loc) 5.96 kB
const {DateTime} = require('luxon'); const {formatTokens} = require('./../interface'); const {icons} = require('./../interface'); const asRelative = n => n.toRelative({locale: 'en'}); const blocksAsEpoch = blocks => Date.now() + blocks * 1000 * 60 * 10; const escape = text => text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\\$&'); const flatten = arr => [].concat(...arr); const fromNow = ms => !ms ? undefined : DateTime.fromMillis(ms); const nodeAlias = (alias, id) => `${alias} ${id.substring(0, 8)}`.trim(); const sumOf = arr => arr.reduce((sum, n) => sum + n, Number()); const uniq = arr => Array.from(new Set(arr)); /** Notify of pending channels and HTLCs { count: <Nodes Count Number> htlcs: [{ forwarding: [{ fee: <Routing Fee Tokens Number> in_peer: <Inbound Peer Public Key Hex String> out_peer: <Outbound Peer Public Key Hex String> tokens: <Routing Tokens Number> }] from: <From Node Named String> nodes: [{ alias: <Node Alias String> id: <Public Key Hex String> }] sending: [{ out_peer: <Outbound Peer Public Key Hex String> }] }] pending: [{ closing: [{ partner_public_key: <Peer Public Key Hex String> pending_balance: <Pending Balance Tokens Number> timelock_expiration: <Funds Locked Until Height Number> }] from: <From Node Named String> height: <Current Block Height Number> nodes: [{ alias: <Node Alias String> id: <Public Key Hex String> }] opening: [{ is_partner_initiated: <Opening Channel is Peer Initiated Bool> local_balance: <Opening Channel Local Balance Tokens Number> partner_public_key: <Opening Channel With Public Key Hex String> remote_balance: <Opening Channel Remote Balance Tokens Number> transaction_fee: <Commitment Transaction Fee Tokens Number> transaction_id: <Funding Transaction Id Hex String> }] }] } @returns <Pending Item String> */ module.exports = ({count, htlcs, pending}) => { // Pending closing and opening channels const channels = pending.map(node => { // Opening channels, waiting for confirmation const openingChannels = node.opening .map(opening => { const direction = !!opening.is_partner_initiated ? 'in' : 'out'; const funds = [opening.local_balance, opening.remote_balance]; const peerId = opening.partner_public_key; const tx = opening.transaction_id; const waiting = `${icons.opening} Waiting`; const capacity = sumOf(funds.concat(opening.transaction_fee)); const peer = node.nodes.find(n => n.id === peerId); const alias = escape(nodeAlias(peer.alias, peer.id)); const channel = `${formatTokens({tokens: capacity}).display} channel`; const action = `${direction}bound ${escape(channel)}`; return `${waiting} for ${action} with ${alias} to confirm: \`${tx}\``; }); // Closing channels, waiting for coins to return const waitingOnFunds = node.closing .filter(n => !!n.timelock_expiration && !!n.pending_balance) .filter(n => n.timelock_expiration > node.height) .map(closing => { const funds = formatTokens({tokens: closing.pending_balance}).display; const peerId = closing.partner_public_key; const waitBlocks = closing.timelock_expiration - node.height; const waiting = `${icons.closing} Waiting`; const peer = node.nodes.find(n => n.id === peerId); const time = escape(asRelative(fromNow(blocksAsEpoch(waitBlocks)))); const action = `recover ${escape(funds)} ${time} from closing channel`; const alias = nodeAlias(peer.alias, peer.id); return `${waiting} to ${action} with ${escape(alias)}`; }); return { from: node.from, channels: flatten([].concat(openingChannels).concat(waitingOnFunds)), }; }); // HTLCs in flight for probing or for forwarding const payments = htlcs.map(node => { // Forwarding an HTLC in one peer and out another const forwarding = node.forwarding.map(forward => { const fee = escape(formatTokens({tokens: forward.fee}).display); const from = node.nodes.find(n => n.id === forward.in_peer); const to = node.nodes.find(n => n.id === forward.out_peer); const tokens = escape(formatTokens({tokens: forward.tokens}).display); const action = `${tokens} for ${fee} fee`; const forwarding = `${icons.forwarding} Forwarding`; const inPeer = escape(nodeAlias(from.alias, from.id)); const outPeer = escape(nodeAlias(to.alias, to.id)); return `${forwarding} ${action} from ${inPeer} to ${outPeer}`; }); // Probing out peers const probes = uniq(node.sending.map(n => n.out_peer)).map(key => { const out = node.nodes.find(n => n.id === key); return escape(nodeAlias(out.alias, out.id)); }); const probing = !probes.length ? [] : [`${icons.probe} Probing out ${probes.join(', ')}`]; return {from: node.from, payments: [].concat(forwarding).concat(probing)}; }); const nodes = []; // Pending channels for a node channels.filter(node => !!node.channels.length).forEach(node => { return node.channels.forEach(item => nodes.push({item, from: node.from})); }); // Pending payments for a node payments.filter(n => !!n.payments.length).forEach(node => { return node.payments.forEach(item => nodes.push({item, from: node.from})); }); // Exit early when there is nothing pending for any nodes if (!nodes.length) { return [`${icons.bot} No pending payments or channels`]; } const sections = uniq(nodes.map(n => n.from)); return flatten(sections.map(from => { const title = (count <= [from].length) ? [] : [`\n*${escape(from)}*`]; return title.concat(nodes.filter(n => n.from === from).map(n => n.item)); })); };