mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add lnurl support for send destination
This commit is contained in:
parent
fdcd62118d
commit
6115a29059
10 changed files with 451 additions and 209 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Versions
|
||||
|
||||
## 12.8.0
|
||||
|
||||
- `send`: Add support for specifying a LNURL or lightning.address to send to
|
||||
|
||||
## 12.7.1
|
||||
|
||||
- `swap`: Add keysend support to swap for pushing swap requests
|
||||
|
|
|
|||
2
bos
2
bos
|
|
@ -1506,7 +1506,7 @@ prog
|
|||
.help('Formulas supported in amount, and N*USD or N*EUR')
|
||||
.help('Also supported in formulas: LIQUIDITY, INBOUND, OUTBOUND (with peer)')
|
||||
.help('OUT_INBOUND, OUT_OUTBOUND (when specifying outbound peer)')
|
||||
.argument('<to>', 'Send to node with public key, or zero amount pay request')
|
||||
.argument('<to>', 'Send to public key, zero pay request, lnurl, ln.address')
|
||||
.option('--amount <amount>', 'Amount to send to destination', STRING, '1')
|
||||
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
||||
.option('--dryrun', 'Avoid actually sending funds')
|
||||
|
|
|
|||
75
lnurl/get_lnurl_request.js
Normal file
75
lnurl/get_lnurl_request.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const getPayRequest = require('./get_pay_request');
|
||||
const getPayTerms = require('./get_pay_terms');
|
||||
const parseUrl = require('./parse_url');
|
||||
|
||||
const tokensAsMtokens = tokens => (BigInt(tokens) * BigInt(1e3)).toString();
|
||||
|
||||
/** Get a LNURL request for a given amount
|
||||
|
||||
{
|
||||
lnurl: <LNUrl String>
|
||||
request: <Request Function>
|
||||
tokens: <Tokens Payment Request String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
destination: <Destination Public Key Hex String>
|
||||
request: <BOLT 11 Payment Request String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({lnurl, request, tokens}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
try {
|
||||
parseUrl({url: lnurl});
|
||||
} catch (err) {
|
||||
return cbk([400, err.message]);
|
||||
}
|
||||
|
||||
if (!request) {
|
||||
return cbk([400, 'ExpectedRequestFunctionToGetLnurlRequest']);
|
||||
}
|
||||
|
||||
if (!tokens) {
|
||||
return cbk([400, 'ExpectedTokensToGetLnurlRequest']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Parse the LNURL into a regular url
|
||||
url: ['validate', ({}, cbk) => {
|
||||
return cbk(null, parseUrl({url: lnurl}).url);
|
||||
}],
|
||||
|
||||
// Get accepted terms from the encoded url
|
||||
getTerms: ['url', ({url}, cbk) => getPayTerms({request, url}, cbk)],
|
||||
|
||||
// Get payment request
|
||||
getRequest: ['getTerms', ({getTerms}, cbk) => {
|
||||
if (tokens > getTerms.max) {
|
||||
return cbk([400, 'PaymentAmountAboveMaximum', {max: getTerms.max}]);
|
||||
}
|
||||
|
||||
if (tokens < getTerms.min) {
|
||||
return cbk([400, 'PaymentAmountBelowMinimum', {min: getTerms.min}]);
|
||||
}
|
||||
|
||||
return getPayRequest({
|
||||
request,
|
||||
hash: getTerms.hash,
|
||||
mtokens: tokensAsMtokens(tokens),
|
||||
url: getTerms.url,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'getRequest'}, cbk));
|
||||
});
|
||||
};
|
||||
96
lnurl/get_pay_request.js
Normal file
96
lnurl/get_pay_request.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {parsePaymentRequest} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const errorStatus = 'ERROR';
|
||||
|
||||
/** Get a payment request for a LNURL
|
||||
|
||||
{
|
||||
hash: <Hash Hex String>
|
||||
mtokens: <Millitokens For Payment Request String>
|
||||
request: <Request Function>
|
||||
url: <URL String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
destination: <Destination Public Key Hex String>
|
||||
request: <BOLT 11 Payment Request String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({hash, mtokens, request, url}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!hash) {
|
||||
return cbk([400, 'ExpectedDescriptionHashToGetLnurlPayRequest']);
|
||||
}
|
||||
|
||||
if (!mtokens) {
|
||||
return cbk([400, 'ExpectedMillitokensToGetLnurlPayRequest']);
|
||||
}
|
||||
|
||||
if (!request) {
|
||||
return cbk([400, 'ExpectedRequestFunctionToGetLnurlPayRequest']);
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return cbk([400, 'ExpectedUrlToGetLnurlPayRequest']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get the payment request
|
||||
getRequest: ['validate', ({}, cbk) => {
|
||||
const qs = {amount: mtokens};
|
||||
|
||||
return request({qs, url, json: true}, (err, r, json) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'FailedToGetPaymentRequestFromService', {err}]);
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
return cbk([503, 'ServiceFailedToReturnPayReqJson']);
|
||||
}
|
||||
|
||||
if (json.status === errorStatus) {
|
||||
return cbk([503, 'ServiceReturnedError', {err: json.reason}]);
|
||||
}
|
||||
|
||||
if (!json.pr) {
|
||||
return cbk([503, 'ExpectedPaymentRequestFromService']);
|
||||
}
|
||||
|
||||
try {
|
||||
parsePaymentRequest({request: json.pr});
|
||||
} catch (err) {
|
||||
return cbk([503, 'FailedToParseReturnedPaymentRequest', {err}]);
|
||||
}
|
||||
|
||||
const request = parsePaymentRequest({request: json.pr});
|
||||
|
||||
if (request.description_hash !== hash) {
|
||||
return cbk([503, 'ServiceReturnedInvalidPaymentDescriptionHash']);
|
||||
}
|
||||
|
||||
if (request.is_expired) {
|
||||
return cbk([503, 'ServiceReturnedExpiredPaymentRequest']);
|
||||
}
|
||||
|
||||
if (request.mtokens !== mtokens) {
|
||||
return cbk([503, 'ServiceReturnedIncorrectInvoiceAmount']);
|
||||
}
|
||||
|
||||
return cbk(null, {
|
||||
destination: request.destination,
|
||||
request: json.pr,
|
||||
});
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'getRequest'}, cbk));
|
||||
});
|
||||
};
|
||||
139
lnurl/get_pay_terms.js
Normal file
139
lnurl/get_pay_terms.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
const {createHash} = require('crypto');
|
||||
|
||||
const asyncAuto = require('async/auto');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const {isArray} = Array;
|
||||
const isNumber = n => !isNaN(n);
|
||||
const lowestSendableValue = 1000;
|
||||
const {max} = Math;
|
||||
const minMaxSendable = 1000;
|
||||
const minMinSendable = 1;
|
||||
const mtokensAsTokens = n => Math.floor(n / 1000);
|
||||
const {parse} = JSON;
|
||||
const payRequestTag = 'payRequest';
|
||||
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
||||
const sslProtocol = 'https:';
|
||||
const textPlain = 'text/plain';
|
||||
const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
|
||||
|
||||
/** Get payment terms
|
||||
|
||||
{
|
||||
request: <Request Function>
|
||||
url: <URL String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
description: <Payment Description String>
|
||||
hash: <Expected Description Hash Hex String>
|
||||
max: <Maximum Tokens Number>
|
||||
min: <Minimum Tokens Number>
|
||||
url: <Callback URL String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({request, url}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!request) {
|
||||
return cbk([400, 'ExpectedRequestFunctionToGetPayTerms']);
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return cbk([400, 'ExpectedUrlToGetPayTerms']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get payment terms
|
||||
getTerms: ['validate', ({}, cbk) => {
|
||||
return request({url, json: true}, (err, r, json) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'FailureGettingLnUrlDataFromUrl', {err}]);
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!json.callback) {
|
||||
return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(json.callback);
|
||||
} catch (err) {
|
||||
return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
|
||||
}
|
||||
|
||||
if ((new URL(json.callback)).protocol !== sslProtocol) {
|
||||
return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
|
||||
}
|
||||
|
||||
if (!isNumber(json.maxSendable)) {
|
||||
return cbk([503, 'ExpectedNumericValueForMaxSendable']);
|
||||
}
|
||||
|
||||
if (!json.maxSendable) {
|
||||
return cbk([503, 'ExpectedNonZeroMaxSendableInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (json.maxSendable < minMaxSendable) {
|
||||
return cbk([400, 'MaxSendableValueIsLowerThanSupportedValue']);
|
||||
}
|
||||
|
||||
if (!json.metadata) {
|
||||
return cbk([503, 'ExpectedLnUrlMetadataInLnurlResponse']);
|
||||
}
|
||||
|
||||
try {
|
||||
parse(json.metadata);
|
||||
} catch (err) {
|
||||
return cbk([503, 'ExpectedValidMetadataInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!isArray(parse(json.metadata))) {
|
||||
return cbk([503, 'ExpectedMetadataArrayInLnurlResponse', json]);
|
||||
}
|
||||
|
||||
const [, description] = parse(json.metadata)
|
||||
.filter(isArray)
|
||||
.find(([entry, text]) => entry === textPlain && !!text);
|
||||
|
||||
if (!description) {
|
||||
return cbk([503, 'ExpectedTextPlainEntryInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!isNumber(json.minSendable)) {
|
||||
return cbk([503, 'ExpectedNumericValueForMinSendable']);
|
||||
}
|
||||
|
||||
if (json.minSendable < minMinSendable) {
|
||||
return cbk([503, 'ExpectedHigherMinSendableValueInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (json.minSendable > json.maxSendable) {
|
||||
return cbk([503, 'ExpectedMaxSendableMoreThanMinSendable']);
|
||||
}
|
||||
|
||||
if (json.tag !== payRequestTag) {
|
||||
return cbk([503, 'ExpectedPaymentRequestTagInLnurlResponse']);
|
||||
}
|
||||
|
||||
return cbk(null, {
|
||||
description,
|
||||
hash: sha256(utf8AsBuffer(json.metadata)),
|
||||
max: mtokensAsTokens(json.maxSendable),
|
||||
min: mtokensAsTokens(max(lowestSendableValue, json.minSendable)),
|
||||
url: json.callback,
|
||||
});
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'getTerms'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
const getLnurlRequest = require('./get_lnurl_request');
|
||||
const manageLnurl = require('./manage_lnurl');
|
||||
const parseUrl = require('./parse_url');
|
||||
|
||||
module.exports = {manageLnurl};
|
||||
module.exports = {getLnurlRequest, manageLnurl, parseUrl};
|
||||
|
|
|
|||
190
lnurl/pay.js
190
lnurl/pay.js
|
|
@ -1,44 +1,35 @@
|
|||
const {createHash} = require('crypto');
|
||||
|
||||
const asyncAuto = require('async/auto');
|
||||
const {getNodeAlias} = require('ln-sync');
|
||||
const moment = require('moment');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
const {parsePaymentRequest} = require('ln-service');
|
||||
|
||||
const getPayRequest = require('./get_pay_request');
|
||||
const getPayTerms = require('./get_pay_terms');
|
||||
const parseUrl = require('./parse_url');
|
||||
const {pay} = require('./../network');
|
||||
|
||||
const errorStatus = 'ERROR';
|
||||
const {isArray} = Array;
|
||||
const isNumber = n => !isNaN(n);
|
||||
const lowestSendableValue = 1000;
|
||||
const {max} = Math;
|
||||
const minMaxSendable = 1000;
|
||||
const minMinSendable = 1;
|
||||
const mtokensAsTokens = n => Math.floor(n / 1000);
|
||||
const {parse} = JSON;
|
||||
const payRequestTag = 'payRequest';
|
||||
const {round} = Math;
|
||||
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
||||
const sslProtocol = 'https:';
|
||||
const textPlain = 'text/plain';
|
||||
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
||||
const tokensAsMillitokens = n => n * 1000;
|
||||
const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
|
||||
|
||||
/** Pay to lnurl
|
||||
{
|
||||
ask: <Ask Function>
|
||||
avoid: [<Avoid Forwarding Through String>]
|
||||
lnd: <Authenticated LND API Object>
|
||||
lnurl: <Lnurl String>
|
||||
logger: <Winston Logger Object>
|
||||
max_fee: <Max Fee Tokens Number>
|
||||
max_paths: <Maximum Paths Number>
|
||||
out: [<Out Through Peer With Public Key Hex String>]
|
||||
request: <Request Function>
|
||||
}
|
||||
|
||||
{
|
||||
ask: <Ask Function>
|
||||
avoid: [<Avoid Forwarding Through String>]
|
||||
lnd: <Authenticated LND API Object>
|
||||
lnurl: <Lnurl String>
|
||||
logger: <Winston Logger Object>
|
||||
max_fee: <Max Fee Tokens Number>
|
||||
max_paths: <Maximum Paths Number>
|
||||
out: [<Out Through Peer With Public Key Hex String>]
|
||||
request: <Request Function>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -92,89 +83,11 @@ module.exports = (args, cbk) => {
|
|||
|
||||
// Get accepted terms from the encoded url
|
||||
getTerms: ['validate', ({}, cbk) => {
|
||||
const {url} = parseUrl({url: args.lnurl});
|
||||
|
||||
return args.request({url, json: true}, (err, r, json) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'FailureGettingLnUrlDataFromUrl', {err}]);
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!json.callback) {
|
||||
return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(json.callback);
|
||||
} catch (err) {
|
||||
return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
|
||||
}
|
||||
|
||||
if ((new URL(json.callback)).protocol !== sslProtocol) {
|
||||
return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
|
||||
}
|
||||
|
||||
if (!isNumber(json.maxSendable)) {
|
||||
return cbk([503, 'ExpectedNumericValueForMaxSendable']);
|
||||
}
|
||||
|
||||
if (!json.maxSendable) {
|
||||
return cbk([503, 'ExpectedNonZeroMaxSendableInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (json.maxSendable < minMaxSendable) {
|
||||
return cbk([400, 'MaxSendableValueIsLowerThanSupportedValue']);
|
||||
}
|
||||
|
||||
if (!json.metadata) {
|
||||
return cbk([503, 'ExpectedLnUrlMetadataInLnurlResponse']);
|
||||
}
|
||||
|
||||
try {
|
||||
parse(json.metadata);
|
||||
} catch (err) {
|
||||
return cbk([503, 'ExpectedValidMetadataInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!isArray(parse(json.metadata))) {
|
||||
return cbk([503, 'ExpectedMetadataArrayInLnurlResponse', json]);
|
||||
}
|
||||
|
||||
const [, description] = parse(json.metadata)
|
||||
.filter(isArray)
|
||||
.find(([entry, text]) => entry === textPlain && !!text);
|
||||
|
||||
if (!description) {
|
||||
return cbk([503, 'ExpectedTextPlainEntryInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (!isNumber(json.minSendable)) {
|
||||
return cbk([503, 'ExpectedNumericValueForMinSendable']);
|
||||
}
|
||||
|
||||
if (json.minSendable < minMinSendable) {
|
||||
return cbk([503, 'ExpectedHigherMinSendableValueInLnurlResponse']);
|
||||
}
|
||||
|
||||
if (json.minSendable > json.maxSendable) {
|
||||
return cbk([503, 'ExpectedMaxSendableMoreThanMinSendable']);
|
||||
}
|
||||
|
||||
if (json.tag !== payRequestTag) {
|
||||
return cbk([503, 'ExpectedPaymentRequestTagInLnurlResponse']);
|
||||
}
|
||||
|
||||
return cbk(null, {
|
||||
description,
|
||||
hash: sha256(utf8AsBuffer(json.metadata)),
|
||||
max: mtokensAsTokens(json.maxSendable),
|
||||
min: mtokensAsTokens(max(lowestSendableValue, json.minSendable)),
|
||||
url: json.callback,
|
||||
});
|
||||
});
|
||||
return getPayTerms({
|
||||
request: args.request,
|
||||
url: parseUrl({url: args.lnurl}).url,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Ask the user for how much they want to send
|
||||
|
|
@ -217,57 +130,18 @@ module.exports = (args, cbk) => {
|
|||
|
||||
// Get payment request
|
||||
getRequest: ['askAmount', 'getTerms', ({askAmount, getTerms}, cbk) => {
|
||||
const qs = {amount: askAmount};
|
||||
const {url} = getTerms;
|
||||
|
||||
return args.request({url, qs, json: true}, (err, r, json) => {
|
||||
if (!!err) {
|
||||
return cbk([503, 'FailedToGetPaymentRequestFromService', {err}]);
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
return cbk([503, 'ServiceFailedToReturnPayReqJson']);
|
||||
}
|
||||
|
||||
if (json.status === errorStatus) {
|
||||
return cbk([503, 'ServiceReturnedError', {err: json.reason}]);
|
||||
}
|
||||
|
||||
if (!json.pr) {
|
||||
return cbk([503, 'ExpectedPaymentRequestFromService']);
|
||||
}
|
||||
|
||||
try {
|
||||
parsePaymentRequest({request: json.pr});
|
||||
} catch (err) {
|
||||
return cbk([503, 'FailedToParseReturnedPaymentRequest', {err}]);
|
||||
}
|
||||
|
||||
const request = parsePaymentRequest({request: json.pr});
|
||||
|
||||
if (request.description_hash !== getTerms.hash) {
|
||||
return cbk([503, 'ServiceReturnedInvalidPaymentDescriptionHash']);
|
||||
}
|
||||
|
||||
if (request.is_expired) {
|
||||
return cbk([503, 'ServiceReturnedExpiredPaymentRequest']);
|
||||
}
|
||||
|
||||
if (request.mtokens !== askAmount.toString()) {
|
||||
return cbk([503, 'ServiceReturnedIncorrectInvoiceAmount']);
|
||||
}
|
||||
|
||||
return cbk(null, json.pr);
|
||||
});
|
||||
return getPayRequest({
|
||||
hash: getTerms.hash,
|
||||
mtokens: askAmount.toString(),
|
||||
request: args.request,
|
||||
url: getTerms.url,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get the destination node alias
|
||||
getAlias: ['getRequest', ({getRequest}, cbk) => {
|
||||
return getNodeAlias({
|
||||
id: parsePaymentRequest({request: getRequest}).destination,
|
||||
lnd: args.lnd,
|
||||
},
|
||||
cbk);
|
||||
return getNodeAlias({id: getRequest.destination, lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Confirm payment
|
||||
|
|
@ -277,12 +151,12 @@ module.exports = (args, cbk) => {
|
|||
'getTerms',
|
||||
({getAlias, getRequest, getTerms}, cbk) =>
|
||||
{
|
||||
const details = parsePaymentRequest({request: getRequest});
|
||||
const details = parsePaymentRequest({request: getRequest.request});
|
||||
|
||||
args.logger.info({
|
||||
amount: details.safe_tokens,
|
||||
description: getTerms.description,
|
||||
payment_request: getRequest,
|
||||
payment_request: getRequest.request,
|
||||
expires: moment(details.expires_at).fromNow(),
|
||||
});
|
||||
|
||||
|
|
@ -312,7 +186,7 @@ module.exports = (args, cbk) => {
|
|||
max_fee: args.max_fee,
|
||||
max_paths: args.max_paths,
|
||||
out: args.out,
|
||||
request: getRequest,
|
||||
request: getRequest.request,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ const {parsePaymentRequest} = require('ln-service');
|
|||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const {getIgnores} = require('./../routing');
|
||||
const getLnurlRequest = require('./../lnurl/get_lnurl_request');
|
||||
const {getTags} = require('./../tags');
|
||||
const {parseAmount} = require('./../display');
|
||||
const parseUrl = require('./../lnurl/parse_url');
|
||||
const probeDestination = require('./probe_destination');
|
||||
|
||||
const coins = ['BTC', 'LTC'];
|
||||
|
|
@ -153,28 +155,39 @@ module.exports = (args, cbk) => {
|
|||
// Payment details
|
||||
payment: ['validate', ({}, cbk) => {
|
||||
try {
|
||||
const {destination, mtokens} = parsePaymentRequest({
|
||||
request: args.destination,
|
||||
});
|
||||
const {url} = parseUrl({url: args.destination});
|
||||
|
||||
return cbk(null, {lnurl: args.destination});
|
||||
} catch (err) {
|
||||
// Ignore errors, destination isn't a LNURL
|
||||
}
|
||||
|
||||
try {
|
||||
const details = parsePaymentRequest({request: args.destination});
|
||||
|
||||
const {destination, mtokens} = details;
|
||||
|
||||
if (!!BigInt(mtokens)) {
|
||||
return cbk([400, 'ExpectedZeroAmountPayRequestToSendFunds']);
|
||||
}
|
||||
|
||||
return cbk(null, {destination, request: args.destination});
|
||||
} catch (_) {
|
||||
return findKey({
|
||||
lnd: args.lnd,
|
||||
query: args.destination,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, {destination: res.public_key, is_push: true});
|
||||
});
|
||||
} catch (err) {
|
||||
// Ignore errors, destination isn't BOLT 11
|
||||
}
|
||||
|
||||
// Find the key to send to
|
||||
return findKey({
|
||||
lnd: args.lnd,
|
||||
query: args.destination,
|
||||
},
|
||||
(err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, {destination: res.public_key, is_push: true});
|
||||
});
|
||||
}],
|
||||
|
||||
// Get ignores
|
||||
|
|
@ -366,22 +379,61 @@ module.exports = (args, cbk) => {
|
|||
}
|
||||
}],
|
||||
|
||||
// Push the amount to the destination
|
||||
push: [
|
||||
'getIgnores',
|
||||
'getInKey',
|
||||
'getOutKey',
|
||||
// Get LNURL payment request
|
||||
getLnurlRequest: [
|
||||
'parseAmount',
|
||||
'payment',
|
||||
({getIgnores, getInKey, getOutKey, parseAmount, payment}, cbk) =>
|
||||
({parseAmount, payment}, cbk) =>
|
||||
{
|
||||
if (parseAmount.tokens < minTokens) {
|
||||
return cbk([400, 'ExpectedNonZeroAmountToPushPayment']);
|
||||
}
|
||||
|
||||
// Exit early when there is no LNURL to send to
|
||||
if (!payment.lnurl) {
|
||||
return cbk(null, {});
|
||||
}
|
||||
|
||||
return getLnurlRequest({
|
||||
lnurl: payment.lnurl,
|
||||
request: args.request,
|
||||
tokens: parseAmount.tokens,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Final payment details
|
||||
send: [
|
||||
'getLnurlRequest',
|
||||
'parseAmount',
|
||||
'payment',
|
||||
({getLnurlRequest, parseAmount, payment}, cbk) =>
|
||||
{
|
||||
if (parseAmount.tokens < minTokens) {
|
||||
return cbk([400, 'ExpectedNonZeroAmountToPushPayment']);
|
||||
}
|
||||
|
||||
return cbk(null, {
|
||||
destination: getLnurlRequest.destination || payment.destination,
|
||||
is_push: payment.is_push,
|
||||
max_fee: parseAmount.max_fee,
|
||||
request: getLnurlRequest.request || payment.request,
|
||||
tokens: !getLnurlRequest.request ? parseAmount.tokens : undefined,
|
||||
});
|
||||
}],
|
||||
|
||||
// Push the amount to the destination
|
||||
push: [
|
||||
'getIgnores',
|
||||
'getInKey',
|
||||
'getOutKey',
|
||||
'send',
|
||||
({getIgnores, getInKey, getOutKey, send}, cbk) =>
|
||||
{
|
||||
args.logger.info({
|
||||
paying: formatTokens({tokens: parseAmount.tokens}).display,
|
||||
to: payment.destination,
|
||||
max_fee: send.max_fee,
|
||||
paying: formatTokens({tokens: send.tokens}).display,
|
||||
to: send.destination,
|
||||
});
|
||||
|
||||
if (!!args.is_dry_run) {
|
||||
|
|
@ -389,25 +441,25 @@ module.exports = (args, cbk) => {
|
|||
}
|
||||
|
||||
return probeDestination({
|
||||
destination: payment.destination,
|
||||
destination: send.destination,
|
||||
fs: args.fs,
|
||||
ignore: getIgnores.ignore,
|
||||
lnd: args.lnd,
|
||||
logger: args.logger,
|
||||
in_through: getInKey,
|
||||
is_omitting_message_from: args.is_omitting_message_from,
|
||||
is_push: payment.is_push,
|
||||
is_push: send.is_push,
|
||||
is_real_payment: true,
|
||||
max_fee: parseAmount.max_fee,
|
||||
max_fee: send.max_fee,
|
||||
message: args.message,
|
||||
messages: args.quiz_answers.map((answer, i) => ({
|
||||
type: (quizStart + i).toString(),
|
||||
value: utf8AsHex(answer),
|
||||
})),
|
||||
out_through: getOutKey,
|
||||
request: payment.request,
|
||||
request: send.request,
|
||||
timeout_minutes: args.timeout_minutes,
|
||||
tokens: parseAmount.tokens,
|
||||
tokens: send.tokens,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
|
|
|||
40
package-lock.json
generated
40
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "balanceofsatoshis",
|
||||
"version": "12.7.1",
|
||||
"version": "12.8.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "balanceofsatoshis",
|
||||
"version": "12.7.1",
|
||||
"version": "12.8.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alexbosworth/caporal": "1.4.4",
|
||||
|
|
@ -2714,9 +2714,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
||||
"integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
|
||||
"version": "1.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.12.tgz",
|
||||
"integrity": "sha512-az/NhpIwP3K33ILr0T2bso+k2E/SLf8Yidd8mHl0n6sCQ4YdyC8qDhZA6kOPDNDBA56ZnIjngVl0U3jREA0BUA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
|
|
@ -3778,9 +3778,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001334",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001334.tgz",
|
||||
"integrity": "sha512-kbaCEBRRVSoeNs74sCuq92MJyGrMtjWVfhltoHUCW4t4pXFvGjUBrfo47weBRViHkiV3eBYyIsfl956NtHGazw==",
|
||||
"version": "1.0.30001335",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz",
|
||||
"integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4439,9 +4439,9 @@
|
|||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.129",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz",
|
||||
"integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==",
|
||||
"version": "1.4.132",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz",
|
||||
"integrity": "sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
|
|
@ -11911,9 +11911,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
||||
"integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
|
||||
"version": "1.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.12.tgz",
|
||||
"integrity": "sha512-az/NhpIwP3K33ILr0T2bso+k2E/SLf8Yidd8mHl0n6sCQ4YdyC8qDhZA6kOPDNDBA56ZnIjngVl0U3jREA0BUA==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
|
|
@ -12774,9 +12774,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001334",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001334.tgz",
|
||||
"integrity": "sha512-kbaCEBRRVSoeNs74sCuq92MJyGrMtjWVfhltoHUCW4t4pXFvGjUBrfo47weBRViHkiV3eBYyIsfl956NtHGazw==",
|
||||
"version": "1.0.30001335",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz",
|
||||
"integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==",
|
||||
"dev": true
|
||||
},
|
||||
"cbor": {
|
||||
|
|
@ -13299,9 +13299,9 @@
|
|||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.129",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz",
|
||||
"integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==",
|
||||
"version": "1.4.132",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz",
|
||||
"integrity": "sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
|
|
|
|||
|
|
@ -84,5 +84,5 @@
|
|||
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
||||
},
|
||||
"version": "12.7.1"
|
||||
"version": "12.8.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue