add lnurl support for send destination

This commit is contained in:
Alex Bosworth 2022-05-03 22:03:07 -07:00
parent fdcd62118d
commit 6115a29059
No known key found for this signature in database
GPG key ID: E80D2F3F311FD87E
10 changed files with 451 additions and 209 deletions

View file

@ -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
View file

@ -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')

View 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
View 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
View 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));
});
};

View file

@ -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};

View file

@ -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);
}],

View file

@ -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
View file

@ -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": {

View file

@ -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"
}