UNPKG

ln-telegram

Version:
182 lines (148 loc) 5.44 kB
const asyncAuto = require('async/auto'); const asyncMap = require('async/map'); const {getBorderCharacters} = require('table'); const {getChainTransactions} = require('ln-accounting') const {getRebalancePayments} = require('ln-sync'); const renderTable = require('table').table; const {returnResult} = require('asyncjs-util'); const {checkAccess} = require('./../authentication'); const {formatTokens} = require('./../interface'); const border = getBorderCharacters('void'); const dayMs = 1000 * 60 * 60 * 24; const flatten = arr => [].concat(...arr); const formatReport = (from, n) => `⚡️ Spent on ${from}\n\n\`\`\`${n}\`\`\``; const formatReports = reports => reports.join('\n'); const header = ['', 'Day', 'Week']; const {isArray} = Array; const {now} = Date; const paidAmount = tokens => formatTokens({tokens, none: '-'}).display; const sumOf = arr => arr.reduce((sum, n) => sum + n, BigInt(Number())); const tokFromMtok = mtok => Number(BigInt(mtok) / BigInt(1e3)); const weekMs = 1000 * 60 * 60 * 24 * 7; /** Check node costs Syntax of command: /costs { from: <Command From User Id Number> id: <Connected User Id Number> nodes: [{ from: <From Name String> lnd: <Authenticated LND API Object> public_key: <Public Key Hex String> }] reply: <Reply Function> request: <Request Function> working: <Working Function> } */ module.exports = ({from, id, nodes, reply, request, working}, cbk) => { return new Promise((resolve, reject) => { return asyncAuto({ // Check arguments validate: cbk => { if (!from) { return cbk([400, 'ExpectedFromUserIdNumberForCostsCommand']); } if (!reply) { return cbk([400, 'ExpectedReplyFunctionForCostsCommand']); } if (!request) { return cbk([400, 'ExpectedRequestFunctionForCostsCommand']); } if (!working) { return cbk([400, 'ExpectedWorkingFunctionForCostsCommand']); } return cbk(); }, // Authenticate the command caller is authorized to this command checkAccess: ['validate', ({}, cbk) => checkAccess({from, id}, cbk)], // Get rebalance payments getRebalances: ['checkAccess', ({}, cbk) => { const after = new Date(now() - weekMs).toISOString(); const dayStart = new Date(now() - dayMs).toISOString(); const lnds = nodes.map(n => n.lnd); return getRebalancePayments({after, lnds}, (err, res) => { if (!!err) { return cbk(err); } return asyncMap(nodes, (node, cbk) => { const key = node.public_key; const payments = res.payments.filter(n => n.destination === key); const day = payments.filter(n => n.confirmed_at > dayStart); return cbk(null, { day: sumOf(day.map(n => BigInt(n.fee_mtokens))), public_key: node.public_key, week: sumOf(payments.map(n => BigInt(n.fee_mtokens))), }); }, cbk); }); }], // Get chain transactions getTransactions: ['checkAccess', ({}, cbk) => { working(); const after = new Date(now() - weekMs).toISOString(); const dayStart = new Date(now() - dayMs).toISOString(); return asyncMap(nodes, (node, cbk) => { const {lnd} = node; return getChainTransactions({after, lnd, request}, (err, res) => { if (!!err) { return cbk(err); } const transactions = res.transactions .filter(n => !!n.fee) .filter(n => !!n.is_confirmed) .filter(n => n.created_at >= after); const day = transactions.filter(n => n.created_at >= dayStart); return cbk(null, { day: Number(sumOf(day.map(n => BigInt(n.fee)))), public_key: node.public_key, week: Number(sumOf(transactions.map(n => BigInt(n.fee)))), }) }); }, cbk); }], // Determine the reply to send to Telegram response: [ 'getRebalances', 'getTransactions', ({getRebalances, getTransactions}, cbk) => { const reports = getTransactions.map(node => { const key = node.public_key; const {from} = nodes.find(n => n.public_key === key); const paid = getRebalances.find(n => n.public_key === key); // Exit early when there were no costs in the week if (!node.week && !paid.week) { return formatReport(from, '- No fees paid in the past week'); } const rows = [ [ 'Rebalances', paidAmount(tokFromMtok(paid.day)), paidAmount(tokFromMtok(paid.week)), ], [ 'Chain Fees', paidAmount(node.day), paidAmount(node.week), ], ]; const chart = renderTable([header].concat(rows), { border, singleLine: true, }); return formatReport(from, chart); }); return cbk(null, formatReports(reports)); }], // Send response to telegram reply: ['response', ({response}, cbk) => { reply(response); return cbk(); }], }, returnResult({reject, resolve}, cbk)); }); };