UNPKG

balanceofsatoshis

Version:
244 lines (198 loc) 7.64 kB
const {parse} = require('querystring'); const asyncAuto = require('async/auto'); const asyncMapSeries = require('async/mapSeries'); const {findKey} = require('ln-sync'); const lnService = require('ln-service'); const lnSync = require('ln-sync'); const {getChannels} = require('ln-service'); const {getNodeAlias} = require('ln-sync'); const {returnResult} = require('asyncjs-util'); const {calls} = require('./api'); const {assign} = Object; const fromLnSync = 'ln-sync'; const isChannel = n => !!n && /^\d*x\d*x\d*$/.test(n); const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n); const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n); const {keys} = Object; const lower = n => n.toLowerCase(); const methodDetails = (calls, method) => calls.find(n => n.method === method); /** Call the raw API { ask: <Inquirer Function> ({message, name, type}, cbk) => {} lnd: <Authenticated LND API Object> logger: <Winston Logger Object> [method]: <Method to Call String> [params]: [<Querystring Encoded Parameter String>] } @returns via cbk or Promise <Result Object> */ module.exports = ({ask, lnd, logger, method, params}, cbk) => { return new Promise((resolve, reject) => { return asyncAuto({ // Check arguments validate: cbk => { if (!ask) { return cbk([400, 'ExpectedAskFunctionToCallApi']); } if (!lnd) { return cbk([400, 'ExpectedAuthenticatedLndToCallApi']); } if (!logger) { return cbk([400, 'ExpectedLoggerToCallApi']); } if (!!method && !calls.find(n => lower(n.method) === lower(method))) { return cbk([404, 'UnrecognizedMethod']); } return cbk(); }, // Get method getMethod: ['validate', ({}, cbk) => { // Exit early when a method was preselected if (!!method) { const call = calls.find(n => lower(n.method) === lower(method)); return cbk(null, {method: call.method}); } return ask({ choices: calls.map(n => n.method), loop: false, message: 'Select method to call', name: 'method', type: 'select', }, method => cbk(null, method)); }], // Get method arguments getArguments: ['getMethod', ({getMethod}, cbk) => { const {arguments} = calls.find(n => n.method === getMethod.method); // Exit early when there are no arguments for this method if (!arguments) { return cbk(null, []); } const parameters = (params || []) .map(encoded => parse(encoded)) .reduce((sum, n) => assign(sum, n), {}); // Parameters must all match method parameters const unknown = keys(parameters).find(param => { return !arguments.find(({named}) => named === param); }); if (!!unknown) { return cbk([400, 'UnknownParameterProvided', {unknown}]); } return asyncMapSeries(arguments, (argument, cbk) => { const {named} = argument; if (!!parameters[named] && argument.type === 'boolean') { return cbk(null, {[named]: parameters[named] === 'true'}); } if (parameters[named] !== undefined) { return cbk(null, {[named]: parameters[named]}); } if (argument.type === 'boolean') { return ask({ default: false, name: named, message: argument.description, prefix: `[${named}]`, type: 'confirm', }, res => cbk(null, res)); } return ask({ default: () => !!argument.optional ? String() : undefined, message: argument.description, name: named, prefix: `[${named}]`, suffix: !!argument.optional ? ' (Optional)' : String(), type: argument.type || 'input', validate: async input => { const isNumber = argument.type === 'number'; // Exit early on number zero if (!argument.optional && isNumber && input === Number()) { return true; } // Exit early when providing nothing to a required argument if (!argument.optional && !input) { return 'Required parameter'; } // Exit early when providing nothing to an optional argument if (!!argument.optional && !input) { return true; } // Channels must be standard format encoded if (argument.type === 'channel' && !isChannel(input)) { return 'A standard format channel id is required'; } // Hashes must be hex encoded 32 byte hashes if (argument.type === 'hash' && !isHash(input)) { return 'A hex encoded 32 byte hash is required'; } // Public keys must be hex public keys if (argument.type === 'public_key' && !isPublicKey(input)) { // Attempt to give a nice error message with a suggested pubkey try { const lookup = await findKey({ lnd, channels: (await getChannels({lnd})).channels, query: input, }); if (!lookup.public_key) { throw new Error('FailedToFindMatchingPublicKeyForInput'); } const {alias} = await getNodeAlias({ lnd, id: lookup.public_key, }); return `Did you mean ${alias} ${lookup.public_key}?`; } catch (err) { return 'A public key is required'; } } return true; }, }, res => cbk(null, res)); }, cbk); }], // Derive arguments arguments: ['getArguments', ({getArguments}, cbk) => { const arguments = getArguments.reduce((sum, answer) => { keys(answer).forEach(key => { if (answer[key] === Number()) { sum[key] = Number(); } else { sum[key] = answer[key] || undefined; } }); return sum; }, {lnd}); return cbk(null, arguments); }], // Set up subscription subscribe: ['arguments', 'getMethod', ({arguments, getMethod}, cbk) => { // Exit early when this is a request/response API if (!methodDetails(calls, getMethod.method).events) { return cbk(); } const sub = lnService[getMethod.method](arguments); sub.on('error', err => cbk([503, 'ApiSubscriptionError', {err}])); const {events} = methodDetails(calls, getMethod.method); logger.info({listening_for: events}); events.forEach(n => sub.on(n, data => logger.info({[n]: data}))); }], // Call API call: ['arguments', 'getMethod', ({arguments, getMethod}, cbk) => { // Exit early when this is an event-based API if (!!methodDetails(calls, getMethod.method).events) { return cbk(); } if (methodDetails(calls, getMethod.method).from === fromLnSync) { return lnSync[getMethod.method](arguments, cbk); } return lnService[getMethod.method](arguments, cbk); }], }, returnResult({reject, resolve, of: 'call'}, cbk)); }); };