lightning
Version:
Lightning Network client library
207 lines (175 loc) • 6.87 kB
JavaScript
const {createHash} = require('crypto');
const {randomBytes} = require('crypto');
const asyncAuto = require('async/auto');
const {parsePaymentRequest} = require('invoices');
const {returnResult} = require('asyncjs-util');
const {createChainAddress} = require('./../address');
const {isLnd} = require('./../../lnd_requests');
const {routeHintFromRoute} = require('./../../lnd_requests');
const hexAsBuffer = hex => !!hex ? Buffer.from(hex, 'hex') : undefined;
const {isArray} = Array;
const method = 'addHoldInvoice';
const msPerSec = 1e3;
const mtokensAsTokens = mtokens => Number(BigInt(mtokens) / BigInt(1e3));
const noTokens = 0;
const {parse} = Date;
const preimageByteLength = 32;
const {round} = Math;
const sha256 = preimage => createHash('sha256').update(preimage).digest();
const tokensAsMtok = tokens => (BigInt(tokens) * BigInt(1e3)).toString();
const type = 'invoices';
/** Create HODL invoice. This invoice will not settle automatically when an
HTLC arrives. It must be settled separately with the secret preimage.
Warning: make sure to cancel the created invoice before its CLTV timeout.
Requires LND built with `invoicesrpc` tag
Requires `address:write`, `invoices:write` permission
{
[cltv_delta]: <Final CLTV Delta Number>
[description]: <Invoice Description String>
[description_hash]: <Hashed Description of Payment Hex String>
[expires_at]: <Expires At ISO 8601 Date String>
[id]: <Payment Hash Hex String>
[is_fallback_included]: <Is Fallback Address Included Bool>
[is_fallback_nested]: <Is Fallback Address Nested Bool>
[is_including_private_channels]: <Invoice Includes Private Channels Bool>
lnd: <Authenticated LND API Object>
[mtokens]: <Millitokens String>
[routes]: [[{
[base_fee_mtokens]: <Base Routing Fee In Millitokens String>
[channel]: <Standard Format Channel Id String>
[cltv_delta]: <CLTV Blocks Delta Number>
[fee_rate]: <Fee Rate In Millitokens Per Million Number>
public_key: <Forward Edge Public Key Hex String>
}]]
[tokens]: <Tokens Number>
}
@returns via cbk or Promise
{
[chain_address]: <Backup Address String>
created_at: <ISO 8601 Date String>
description: <Description String>
id: <Payment Hash Hex String>
mtokens: <Millitokens Number String>
request: <BOLT 11 Encoded Payment Request String>
[secret]: <Hex Encoded Payment Secret String>
tokens: <Tokens Number>
}
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!isLnd({method, type, lnd: args.lnd})) {
return cbk([400, 'ExpectedInvoicesLndToCreateHodlInvoice']);
}
if (!!args.routes && !isArray(args.routes)) {
return cbk([400, 'ExpectedArrayOfHopHintPathsForRoutes']);
}
return cbk();
},
// Add address for the fallback address
addAddress: ['validate', ({}, cbk) => {
// Exit early when no fallback address is needed
if (!args.is_fallback_included) {
return cbk();
}
const format = !!args.is_fallback_nested ? 'np2wpkh' : 'p2wpkh';
return createChainAddress({format, lnd: args.lnd}, cbk);
}],
// Determine the route hints
hints: ['validate', ({}, cbk) => {
// Exit early when there are no route hints for the invoice
if (!args.routes) {
return cbk();
}
const hints = args.routes.map(route => {
return {hop_hints: routeHintFromRoute({route}).hops};
});
return cbk(null, hints);
}],
// Generate id if needed
invoiceId: ['validate', ({}, cbk) => {
if (!!args.id) {
return cbk(null, {id: args.id});
}
const secret = randomBytes(preimageByteLength);
return cbk(null, {
id: sha256(secret).toString('hex'),
secret: secret.toString('hex'),
});
}],
// Add invoice
addInvoice: [
'addAddress',
'hints',
'invoiceId',
({addAddress, hints, invoiceId}, cbk) =>
{
const fallbackAddress = !addAddress ? undefined : addAddress.address;
const createdAt = new Date();
const expireAt = !args.expires_at ? null : parse(args.expires_at);
const mtokens = !args.tokens ? undefined : tokensAsMtok(args.tokens);
const expiryMs = !expireAt ? null : expireAt - createdAt.getTime();
const invoiceMtok = mtokens || args.mtokens || noTokens.toString();
return args.lnd.invoices.addHoldInvoice({
cltv_expiry: !args.cltv_delta ? undefined : args.cltv_delta,
description_hash: hexAsBuffer(args.description_hash),
expiry: !expiryMs ? undefined : round(expiryMs / msPerSec),
fallback_addr: fallbackAddress,
hash: Buffer.from(invoiceId.id, 'hex'),
memo: args.description,
private: !!args.is_including_private_channels,
route_hints: hints || undefined,
value: args.tokens || undefined,
value_msat: args.mtokens || undefined,
},
(err, response) => {
if (!!err) {
return cbk([503, 'UnexpectedAddHodlInvoiceError', {err}]);
}
if (!response) {
return cbk([503, 'ExpectedResponseWhenAddingHodlInvoice']);
}
if (!response.payment_request) {
return cbk([503, 'ExpectedPaymentRequestForCreatedInvoice']);
}
try {
parsePaymentRequest({request: response.payment_request});
} catch (err) {
return cbk([503, 'ExpectedValidPaymentRequestForHodlInvoice']);
}
const request = response.payment_request;
const parsed = parsePaymentRequest({request});
return cbk(null, {
created_at: parsePaymentRequest({request}).created_at,
description: args.description || undefined,
id: invoiceId.id,
mtokens: invoiceMtok,
request: response.payment_request,
tokens: mtokensAsTokens(invoiceMtok),
});
});
}],
// Final invoice
invoice: [
'addAddress',
'addInvoice',
'invoiceId',
({addAddress, addInvoice, invoiceId}, cbk) =>
{
return cbk(null, {
chain_address: !addAddress ? undefined : addAddress.address,
created_at: addInvoice.created_at,
description: addInvoice.description,
id: invoiceId.id,
mtokens: addInvoice.mtokens,
request: addInvoice.request,
secret: invoiceId.secret || undefined,
tokens: addInvoice.tokens,
});
}],
},
returnResult({reject, resolve, of: 'invoice'}, cbk));
});
};