UNPKG

balanceofsatoshis

Version:
360 lines (292 loc) 10 kB
const asyncAuto = require('async/auto'); const asyncMap = require('async/map'); const {formatTokens} = require('ln-sync'); const {getNodeAlias} = require('ln-sync'); const moment = require('moment'); const {returnResult} = require('asyncjs-util'); const feesForSegment = require('./fees_for_segment'); const {getTags} = require('./../tags'); const getForwards = require('./get_forwards'); const asDate = n => !!n ? n.toISOString() : undefined; const daysBetween = (a, b) => moment(a).diff(b, 'days') + 1; const daysPerWeek = 7; const defaultDays = 60; const {floor} = Math; const hoursCount = (a, b) => moment(a).diff(b, 'hours') + 1; const hoursPerDay = 24; 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 minChartDays = 4; const maxChartDays = 90; const {now} = Date; const parseDate = n => Date.parse(n); /** Get data for fees chart { [days]: <Fees Earned Over Days Count Number> [end_date]: <End Date YYYY-MM-DD String> fs: { getFile: <Get File Function> } is_count: <Return Only Count of Forwards Bool> lnds: [<Authenticated LND API Object>] [start_date]: <Start Date YYYY-MM-DD String> [via]: <Via Public Key Hex or Tag Id or Alias String> } @returns via cbk or Promise { data: [<Earned Fee 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 (!args.fs) { return cbk([400, 'ExpectedFileSystemMethodsToGetFeesChart']); } 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, 'ExpectedStartDateToRangeToEndDateForFeesChart']); } if (!isDate(args.start_date)) { return cbk([400, 'ExpectedValidDateTypeForFeesChartStartDate']); } if (!moment(args.start_date).isValid()) { return cbk([400, 'ExpectedValidStartDateForFeesChartEndDate']); } 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, 'ExpectedStartDateBeforeEndDateForFeesChart']); } if (!isDate(args.end_date)) { return cbk([400, 'ExpectedValidDateFormatForFeesChartEndDate']); } if (!moment(args.end_date).isValid()) { return cbk([400, 'ExpectedValidEndDateForFeesChartEndDate']); } if (parseDate(args.end_date) > now()) { return cbk([400, 'ExpectedPastEndDateToGetFeesChart']); } return cbk(); }, // End date for getting fee earnings end: ['validate', ({}, cbk) => { if (!args.end_date) { return cbk(); } return cbk(null, moment(args.end_date).endOf('day')); }], // Calculate the start date start: ['validate', ({}, cbk) => { if (!!args.start_date) { return cbk(null, moment(args.start_date)); } return cbk(null, moment().subtract(args.days || defaultDays, 'days')); }], // Determine how many days to chart over days: ['validate', ({}, cbk) => { // Exit early when not using a date range if (!args.start_date) { return cbk(null, args.days || defaultDays); } return cbk(null, daysBetween(args.end_date, args.start_date)); }], // Get the list of tags to look for a via match getTags: ['validate', ({}, cbk) => { if (!args.via) { return cbk(); } return getTags({fs: args.fs}, cbk); }], // Segment measure measure: ['days', ({days}, cbk) => { if (days > maxChartDays) { return cbk(null, 'week'); } else if (days < minChartDays) { return cbk(null, 'hour'); } else { return cbk(null, 'day'); } }], // Determine via tag viaTag: ['getTags', ({getTags}, cbk) => { // Exit early when there is no via filter if (!args.via) { return cbk(); } const tagById = getTags.tags.find(({id}) => id === args.via); if (!!tagById) { return cbk(null, tagById); } const tagByAlias = getTags.tags.find(({alias}) => alias === args.via); if (!!tagByAlias) { return cbk(null, tagByAlias); } return cbk(); }], // Get node details getNode: ['viaTag', ({viaTag}, cbk) => { // Exit early when there is no via node specified if (!args.via) { return cbk(); } // Exit early when via is a tag match if (!!viaTag) { return cbk(); } const [lnd] = args.lnds; return getNodeAlias({lnd, id: args.via}, cbk); }], // Fees via nodes via: ['viaTag', ({viaTag}, cbk) => { if (!!viaTag) { return cbk(null, viaTag.nodes); } if (!!args.via) { return cbk(null, [args.via]); } return cbk(); }], // Get forwards getForwards: ['start', 'end', 'via', ({start, end, via}, cbk) => { return asyncMap(args.lnds, (lnd, cbk) => { return getForwards({ lnd, via, after: asDate(start), before: asDate(end), }, cbk); }, cbk); }], // Filter the forwards forwards: ['getForwards', ({getForwards}, cbk) => { const forwards = getForwards.map(({forwards}) => forwards); return cbk(null, forwards.reduce((sum, n) => sum.concat(n), [])); }], // Total earnings totalEarned: ['forwards', ({forwards}, cbk) => { return cbk(null, forwards.reduce((sum, {fee}) => sum + fee, Number())); }], // Total forwarded totalForwarded: ['forwards', ({forwards}, cbk) => { const total = forwards.reduce((sum, n) => sum + n.tokens, Number()); return cbk(null, total); }], // Total number of segments segments: ['days', 'end', 'measure', ({days, end, measure}, cbk) => { switch (measure) { case 'hour': // Exit early when using full days if (!args.start_date) { return cbk(null, hoursPerDay * days); } return cbk(null, hoursCount(end, args.start_date)); case 'week': return cbk(null, floor(days / daysPerWeek)); default: return cbk(null, days); } }], // Forwarding activity aggregated sum: [ 'end', 'forwards', 'measure', 'segments', ({end, forwards, measure, segments}, cbk) => { return cbk(null, feesForSegment({ forwards, measure, segments, end: !!end ? end.toISOString() : undefined, })); }], // Summary description of the fees earned description: [ 'end', 'forwards', 'measure', 'start', 'sum', 'totalEarned', 'totalForwarded', ({end, forwards, measure, start, totalEarned, totalForwarded, sum}, cbk) => { const since = `from ${start.calendar().toLowerCase()}`; const to = !!end ? ` to ${end.calendar().toLowerCase()}` : ''; if (!!args.is_count) { const duration = `Forwarded in ${sum.count.length} ${measure}s`; const forwarded = `Total: ${forwards.length} forwards`; return cbk(null, `${duration} ${since}${to}. ${forwarded}`); } else if (!!args.is_forwarded) { const duration = `Forwarded in ${sum.count.length} ${measure}s`; const {display} = formatTokens({tokens: totalForwarded}); return cbk(null, `${duration} ${since}${to}. Total: ${display}`); } else { const duration = `Earned in ${sum.fees.length} ${measure}s`; const earned = (totalEarned / 1e8).toFixed(8); return cbk(null, `${duration} ${since}${to}. Total: ${earned}`); } }], // Heading head: ['validate', ({}, cbk) => { if (!!args.is_count) { return cbk(null, 'Forwards count'); } if (!!args.is_forwarded) { return cbk(null, 'Forwarded amount'); } return cbk(null, 'Routing fees earned'); }], // Summary title of the fees earned title: ['getNode', 'head', 'viaTag', ({getNode, head, viaTag}, cbk) => { // Exit early when no via is specified if (!args.via) { return cbk(null, head); } const via = viaTag || getNode; return cbk(null, `${head} via ${via.alias || via.id}`); }], // Forwarding activity data: [ 'description', 'sum', 'title', ({description, sum, title}, cbk) => { if (!!args.is_count) { return cbk(null, {description, title, data: sum.count}); } if (!!args.is_forwarded) { return cbk(null, {description, title, data: sum.forwarded}); } return cbk(null, {description, title, data: sum.fees}); }], }, returnResult({reject, resolve, of: 'data'}, cbk)); }); };