paid-services
Version:
Lightning Paid Services library
147 lines (125 loc) • 4.06 kB
JavaScript
const asyncAuto = require('async/auto');
const {parsePaymentRequest} = require('ln-service');
const {payViaRoutes} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {subscribeToProbeForRoute} = require('ln-service');
const cltvDeltaBuffer = 5;
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
/** Buy the preimage for a trade
{
ask: <Ask Function>
lnd: <Authenticated LND API Object>
logger: <Winston Logger Object>
request: <BOLT 11 Encoded Payment Request String>
}
@returns via cbk or Promise
{
fee: <Fee Paid Number>
secret: <Payment Preimage Hex String>
tokens: <Tokens Paid Number>
}
*/
module.exports = ({ask, lnd, logger, request}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!ask) {
return cbk([400, 'ExpectedAskFunctionToBuyTradePreimage']);
}
if (!lnd) {
return cbk([400, 'ExpectedAuthenticatedLndToBuyTradePreimage']);
}
if (!logger) {
return cbk([400, 'ExpectedWinstonLoggerToBuyTradePreimage']);
}
if (!request) {
return cbk([400, 'ExpectedPaymentRequestToPurchaseTradePreimage']);
}
return cbk();
},
// Decode the payment request
parseRequest: ['validate', ({}, cbk) => {
try {
const details = parsePaymentRequest({request});
return cbk(null, {
cltv_delta: details.cltv_delta,
destination: details.destination,
features: details.features,
id: details.id,
mtokens: details.mtokens,
payment: details.payment,
routes: details.routes,
tokens: details.tokens,
});
} catch (err) {
return cbk([400, 'ExpectedValidPaymentRequestToBuyPreimage', {err}]);
}
}],
// Subscribe to a probe to a route
getRoute: ['parseRequest', ({parseRequest}, cbk) => {
const sub = subscribeToProbeForRoute({
lnd,
cltv_delta: parseRequest.cltv_delta + cltvDeltaBuffer,
destination: parseRequest.destination,
features: parseRequest.features,
mtokens: parseRequest.mtokens,
payment: parseRequest.payment,
routes: parseRequest.routes,
total_mtokens: !!parseRequest.payment ? parseRequest.mtokens : null,
});
const done = (err, res) => {
sub.removeAllListeners();
return cbk(err, res);
};
sub.once('end', () => done([503, 'FailedToFindPathToDestination']));
sub.once('error', err => done(err));
sub.once('probe_success', ({route}) => done(null, route));
sub.on('probing', ({route}) => {
return logger.info({checking_path_with_fee: route.fee});
});
return;
}],
// Confirm transaction fee
confirmFee: [
'getRoute',
'parseRequest',
({getRoute, parseRequest}, cbk) =>
{
return ask({
name: 'pay',
message: `Pay ${parseRequest.tokens} and ${getRoute.fee} fee?`,
type: 'confirm',
},
res => cbk(null, res));
}],
// Pay the request
pay: [
'confirmFee',
'getRoute',
'parseRequest',
({confirmFee, getRoute, parseRequest}, cbk) =>
{
if (!confirmFee.pay) {
return cbk([400, 'PaymentCanceled']);
}
logger.info({paying: getRoute.tokens});
return payViaRoutes({
lnd,
id: parseRequest.id,
routes: [getRoute],
},
cbk);
}],
// Final payment
paid: ['parseRequest', 'pay', ({parseRequest, pay}, cbk) => {
return cbk(null, {
fee: tokensAsBigUnit(pay.fee),
secret: pay.secret,
tokens: tokensAsBigUnit(parseRequest.tokens),
});
}],
},
returnResult({reject, resolve, of: 'paid'}, cbk));
});
};