UNPKG

balanceofsatoshis

Version:
265 lines (221 loc) 8.18 kB
const asyncAuto = require('async/auto'); const asyncDetect = require('async/detect'); const asyncFilterLimit = require('async/filterLimit'); const asyncMap = require('async/map'); const {formatTokens} = require('ln-sync'); const {getAllInvoices} = require('ln-sync'); const {getPayment} = require('ln-service'); const moment = require('moment'); const {returnResult} = require('asyncjs-util'); const {segmentMeasure} = require('./../display'); const {sumsForSegment} = require('./../display'); const daysBetween = (a, b) => moment(a).diff(b, 'days') + 1; const defaultDays = 60; const flatten = arr => [].concat(...arr); const {isArray} = Array; const isDate = n => /^\d{4}(-(0[1-9]|1[0-2]))?(-(0[1-9]|[12][0-9]|3[01]))?$/.test(n); const maxGetPayments = 100; const mtokensAsTokens = n => Number(n / BigInt(1e3)); const notFound = 404; const {now} = Date; const parseDate = n => Date.parse(n); /** Get data for received payments chart { [days]: <Received Over Days Count Number> [end_date]: <End Date YYYY-MM-DD String> lnds: [<Authenticated LND API Object>] [query]: <Match Description String> [start_date]: <Start Date YYYY-MM-DD String> } @returns via cbk or Promise { data: [<Received Tokens Number>] description: <Chart Description String> title: <Chart Title String> } */ module.exports = (args, cbk) => { return new Promise((resolve, reject) => { return asyncAuto({ // Check arguments validate: cbk => { if (!isArray(args.lnds)) { return cbk([400, 'ExpectedLndToGetFeesChart']); } if (!args.lnds.length) { return cbk([400, 'ExpectedAnLndToGetFeesChart']); } // Exit early when there is no end date and no start date if (!args.end_date && !args.start_date) { return cbk(); } if (!!args.days) { return cbk([400, 'ExpectedEitherDaysOrDatesToGetFeesChart']); } if (!!args.end_date && !args.start_date) { return cbk([400, 'ExpectedStartDateToRangeToEndDate']); } if (!isDate(args.start_date)) { return cbk([400, 'ExpectedValidDateTypeForReceivedChartStartDate']); } if (!moment(args.start_date).isValid()) { return cbk([400, 'ExpectedValidEndDateForReceivedChartEndDate']); } if (parseDate(args.start_date) > now()) { return cbk([400, 'ExpectedPastStartDateToGetFeesChart']); } // Exit early when there is no end date if (!args.end_date) { return cbk(); } if (args.start_date > args.end_date) { return cbk([400, 'ExpectedStartDateBeforeEndDateToGetFeesChart']); } if (!isDate(args.end_date)) { return cbk([400, 'ExpectedValidDateFormatToForChartEndDate']); } if (!moment(args.end_date).isValid()) { return cbk([400, 'ExpectedValidEndDateForReceivedChartEndDate']); } if (parseDate(args.end_date) > now()) { return cbk([400, 'ExpectedPastEndDateToGetFeesChart']); } return cbk(); }, // End date for received payments end: ['validate', ({}, cbk) => { if (!args.end_date) { return cbk(); } return cbk(null, moment(args.end_date).endOf('day')); }], // Segment measure segment: ['end', ({end}, cbk) => { // Exit early when not looking at a date range if (!args.start_date && !args.end_date) { return cbk(null, segmentMeasure({days: args.days || defaultDays})); } const days = daysBetween(end, args.start_date); return cbk(null, segmentMeasure({ days, end: !!end ? end.toISOString() : undefined, start: args.start_date, })); }], // Start date for received payments start: ['validate', ({}, cbk) => { if (!!args.start_date) { return cbk(null, moment(args.start_date)); } return cbk(null, moment().subtract(args.days || defaultDays, 'days')); }], // Get all the settled invoices using a subscription getSettled: ['end', 'start', ({end, start}, cbk) => { return asyncMap(args.lnds, (lnd, cbk) => { return getAllInvoices({ lnd, confirmed_after: start.toISOString(), created_after: start.toISOString(), }, cbk); }, (err, res) => { if (!!err) { return cbk(err); } const settled = flatten(res.map(n => n.invoices)).filter(invoice => { if (!!args.query && !invoice.description.includes(args.query)) { return false; } // Exit early when considering all invoices without an end point if (!args.end_date) { return true; } return moment(invoice.confirmed_at).isSameOrBefore(end, 'day'); }); return cbk(null, settled); }); }], // Eliminate self-payments by looking for payments with invoice ids getReceived: ['getSettled', ({getSettled}, cbk) => { return asyncFilterLimit(getSettled, maxGetPayments, (invoice, cbk) => { return asyncMap(args.lnds, (lnd, cbk) => { return getPayment({id: invoice.id, lnd}, (err, res) => { if (isArray(err) && err.shift() === notFound) { return cbk(null, false); } if (!!err) { return cbk(err); } return cbk(null, res.payment); }); }, (err, payments) => { if (!!err) { return cbk(err); } return cbk(null, !payments.filter(n => !!n).length); }); }, cbk); }], // Sum all of the invoices received amounts totalReceived: ['getReceived', ({getReceived}, cbk) => { const total = getReceived.reduce((sum, invoice) => { return sum + BigInt(invoice.received_mtokens); }, BigInt(Number())); return cbk(null, mtokensAsTokens(total)); }], // Earnings aggregated sum: ['end', 'getReceived', 'segment', ({end, getReceived, segment}, cbk) => { return cbk(null, sumsForSegment({ end: !!end ? end.toISOString() : undefined, measure: segment.measure, records: getReceived.map(invoice => { return {date: invoice.confirmed_at, tokens: invoice.received}; }), segments: segment.segments, })); }], // Summary description of the received payments description: [ 'end', 'getReceived', 'segment', 'start', 'sum', 'totalReceived', ({end, getReceived, segment, start, totalReceived, sum}, cbk) => { const action = 'Received in'; const {measure} = segment; const since = `from ${start.calendar().toLowerCase()}`; const to = !!end ? ` to ${end.calendar().toLowerCase()}` : ''; if (!!args.is_count) { const duration = `${action} ${sum.count.length} ${measure}s`; const total = `Total: ${getReceived.length} received payments`; return cbk(null, `${duration} ${since}${to}. ${total}`); } else { const duration = `${action} ${sum.sum.length} ${measure}s`; const total = formatTokens({tokens: totalReceived}).display || '0'; return cbk(null, `${duration} ${since}${to}. Total: ${total}`); } }], // Total activity data: ['description', 'sum', ({description, sum}, cbk) => { const title = [ !!args.is_count ? 'Received' : 'Payments', !!args.query ? `for “${args.query}”` : '', !!args.is_count ? 'count' : 'received', ]; return cbk(null, { description, data: !args.is_count ? sum.sum : sum.count, title: title.filter(n => !!n).join(' '), }); }], }, returnResult({reject, resolve, of: 'data'}, cbk)); }); };