fix issues with find, tags, service keysend reqs

This commit is contained in:
Alex Bosworth 2021-03-26 20:50:49 -07:00
parent d15d0435be
commit 8cfd134db5
No known key found for this signature in database
GPG key ID: E80D2F3F311FD87E
13 changed files with 461 additions and 54 deletions

View file

@ -1,5 +1,11 @@
# Versions
## Version 8.0.3
- `find`: Lookup node by start of public key
- `service-keysend-requests`: Use an alternative node to pay response requests
- `tags`: Fix issue when setting avoid on a tag without adjusting icon or keys
## Version 8.0.2
- `rebalance`: Show node icons in route descriptions

33
bos
View file

@ -1338,14 +1338,43 @@ prog
// Respond to keysend service requests
.command('service-keysend-requests', 'Respond to keysend service requests')
.help('Offering keysend services requires --keysend enabled')
.help('Offering keysend services requires --accept-keysend in LND')
.option('--node <node_name>', 'Saved node to respond to service requests')
.option('--payer <pay_with_node_name>', 'Node to pay response requests with')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
const {lnd} = await lndForNode(logger, options.node);
const payer = options.payer === options.node ? null : options.payer;
await services.serviceKeySendRequests({
lnd,
logger,
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
pay: !payer ? lnd : (await lndForNode(logger, payer)).lnd,
},
responses.returnObject({logger, reject, resolve}));
} catch (err) {
return reject(logger.error({err}));
}
});
})
// Simulate a keysend request by generating a special local invoice
.command('simulate-keysend-request', 'Create an invoice to simulate request')
.visible(false)
.argument('<amount>', 'Invoice amount to simulate receiving')
.argument('<number_value_pairs...>', 'TLV records: <number> <hex value>')
.help('Make an invoice that represents a fake keysend with records')
.help('This invoice can trigger keysend request services to test them out')
.option('--node <node_name>', 'Saved node to create simulated request')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
await services.simulateKeySendRequest({
lnd: (await lndForNode(logger, options.node)).lnd,
tokens: Number(args.amount),
types: args.numberValuePairs.filter((n, i) => !(i % 2)),
values: args.numberValuePairs.filter((n, i) => i % 2),
},
responses.returnObject({logger, reject, resolve}));
} catch (err) {

View file

@ -135,6 +135,10 @@ module.exports = ({lnd, query}, cbk) => {
return true;
}
if (node.public_key.startsWith(query.toLowerCase())) {
return true;
}
return node.public_key === query;
})
.map(node => {

View file

@ -155,10 +155,11 @@ module.exports = (args, cbk) => {
return cbk([400, 'AmbiguousTagToAdjustSpecified', {matches}]);
}
const setAvoid = args.is_avoided !== undefined;
const setIcon = args.icon !== undefined;
// Exit early when not editing the tag
if (!args.add.length && !setIcon && !args.remove.length) {
if (!args.add.length && !setAvoid && !setIcon && !args.remove.length) {
return cbk(null, tagMatch);
}

176
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "balanceofsatoshis",
"version": "8.0.2",
"version": "8.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -901,6 +901,14 @@
}
}
},
"bolt01": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/bolt01/-/bolt01-1.2.0.tgz",
"integrity": "sha512-CPpRbmi+4CU63Mt87gitgJz5YgygirvZuyJOBzTVH0uXCtav5rl94jzsR/m+ihfYFlswvebNwy7dX/w+YV+BZg==",
"requires": {
"bn.js": "5.2.0"
}
},
"bolt03": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/bolt03/-/bolt03-1.2.7.tgz",
@ -1138,9 +1146,9 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"cbor": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-7.0.3.tgz",
"integrity": "sha512-Io+lJytjYBJKgJqZQUz2bFaMPj+HtlsnT9kHSUiIJFqzWa05lm5/ycQ+NiZWpks3DR2Fz7j7axiTGeT57w/syg==",
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-7.0.4.tgz",
"integrity": "sha512-9hBTn31l7+9qteBso7+HPp2R5ytqFRBd98fHK4ZTpvrba8V7CuoOsEL0S6vf7+11gubMTd3RW97lOgMTl5SNfg==",
"requires": {
"@cto.af/textdecoder": "^0.0.0",
"nofilter": "^2.0.3"
@ -2129,6 +2137,32 @@
"node-fetch": "2.6.1",
"pushdata-bitcoin": "1.0.1",
"varuint-bitcoin": "1.1.2"
},
"dependencies": {
"cbor": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-7.0.3.tgz",
"integrity": "sha512-Io+lJytjYBJKgJqZQUz2bFaMPj+HtlsnT9kHSUiIJFqzWa05lm5/ycQ+NiZWpks3DR2Fz7j7axiTGeT57w/syg==",
"requires": {
"@cto.af/textdecoder": "^0.0.0",
"nofilter": "^2.0.3"
}
},
"ln-service": {
"version": "51.3.1",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.3.1.tgz",
"integrity": "sha512-V5Q1rZbN4GY1PfxosQ1pWyyhZF/qPWivf5whL0lDWBrJ4xgsemYRkg5ce4YaUNLYv/BXfMrQVyw09w+tyq+QnQ==",
"requires": {
"bolt07": "1.7.1",
"cors": "2.8.5",
"express": "4.17.1",
"invoices": "1.1.6",
"lightning": "3.2.20",
"macaroon": "3.0.4",
"morgan": "1.10.0",
"ws": "7.4.4"
}
}
}
},
"google-auth-library": {
@ -2946,6 +2980,15 @@
"version": "14.14.32",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.32.tgz",
"integrity": "sha512-/Ctrftx/zp4m8JOujM5ZhwzlWLx22nbQJiVqz8/zE15gOeEW+uly3FSX4fGFpcfEvFzXcMCJwq9lGVWgyARXhg=="
},
"cbor": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-7.0.3.tgz",
"integrity": "sha512-Io+lJytjYBJKgJqZQUz2bFaMPj+HtlsnT9kHSUiIJFqzWa05lm5/ycQ+NiZWpks3DR2Fz7j7axiTGeT57w/syg==",
"requires": {
"@cto.af/textdecoder": "^0.0.0",
"nofilter": "^2.0.3"
}
}
}
},
@ -2960,21 +3003,79 @@
"goldengate": "10.0.2",
"json2csv": "5.0.6",
"ln-service": "51.3.1"
},
"dependencies": {
"ln-service": {
"version": "51.3.1",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.3.1.tgz",
"integrity": "sha512-V5Q1rZbN4GY1PfxosQ1pWyyhZF/qPWivf5whL0lDWBrJ4xgsemYRkg5ce4YaUNLYv/BXfMrQVyw09w+tyq+QnQ==",
"requires": {
"bolt07": "1.7.1",
"cors": "2.8.5",
"express": "4.17.1",
"invoices": "1.1.6",
"lightning": "3.2.20",
"macaroon": "3.0.4",
"morgan": "1.10.0",
"ws": "7.4.4"
}
}
}
},
"ln-service": {
"version": "51.3.1",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.3.1.tgz",
"integrity": "sha512-V5Q1rZbN4GY1PfxosQ1pWyyhZF/qPWivf5whL0lDWBrJ4xgsemYRkg5ce4YaUNLYv/BXfMrQVyw09w+tyq+QnQ==",
"version": "51.5.0",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.5.0.tgz",
"integrity": "sha512-Q22CcKJ+52mExGNvOmeEVBza82LGdbLkFVzfMjC9oPlP5DikS+sdlK5GZM/Dc5HFN5pwK7y9Ke7gRue/UxHwbw==",
"requires": {
"bolt07": "1.7.1",
"cors": "2.8.5",
"express": "4.17.1",
"invoices": "1.1.6",
"lightning": "3.2.20",
"lightning": "3.3.1",
"macaroon": "3.0.4",
"morgan": "1.10.0",
"ws": "7.4.4"
},
"dependencies": {
"@grpc/grpc-js": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.2.11.tgz",
"integrity": "sha512-DZqx3nHBm2OGY7NKq4sppDEfx4nBAsQH/d/H/yxo/+BwpVLWLGs+OorpwQ+Fqd6EgpDEoi4MhqndjGUeLl/5GA==",
"requires": {
"@types/node": ">=12.12.47",
"google-auth-library": "^6.1.1",
"semver": "^6.2.0"
}
},
"@types/node": {
"version": "14.14.35",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
"integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag=="
},
"lightning": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/lightning/-/lightning-3.3.1.tgz",
"integrity": "sha512-lzj7oODpX7XS/4qHGBG/VAMirBDK/rbWS8S9pTCSUV9ZknuXg2loCkDyPQuB3vC2MMt1QOsXn4oQUtxpPtSa1g==",
"requires": {
"@grpc/grpc-js": "1.2.11",
"@grpc/proto-loader": "0.5.6",
"@types/express": "4.17.11",
"@types/node": "14.14.35",
"@types/request": "2.48.5",
"@types/ws": "7.4.0",
"async": "3.2.0",
"asyncjs-util": "1.2.3",
"bitcoinjs-lib": "5.2.0",
"bn.js": "5.2.0",
"body-parser": "1.19.0",
"bolt07": "1.7.1",
"bolt09": "0.1.3",
"cbor": "7.0.4",
"express": "4.17.1",
"invoices": "1.1.6",
"psbt": "1.1.8"
}
}
}
},
"ln-sync": {
@ -2989,6 +3090,17 @@
"colorette": "1.2.2",
"lightning": "3.2.20",
"stats-lite": "2.2.0"
},
"dependencies": {
"cbor": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-7.0.3.tgz",
"integrity": "sha512-Io+lJytjYBJKgJqZQUz2bFaMPj+HtlsnT9kHSUiIJFqzWa05lm5/ycQ+NiZWpks3DR2Fz7j7axiTGeT57w/syg==",
"requires": {
"@cto.af/textdecoder": "^0.0.0",
"nofilter": "^2.0.3"
}
}
}
},
"ln-telegram": {
@ -3004,6 +3116,23 @@
"ln-sync": "0.4.4",
"moment": "2.29.1",
"table": "6.0.7"
},
"dependencies": {
"ln-service": {
"version": "51.3.1",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.3.1.tgz",
"integrity": "sha512-V5Q1rZbN4GY1PfxosQ1pWyyhZF/qPWivf5whL0lDWBrJ4xgsemYRkg5ce4YaUNLYv/BXfMrQVyw09w+tyq+QnQ==",
"requires": {
"bolt07": "1.7.1",
"cors": "2.8.5",
"express": "4.17.1",
"invoices": "1.1.6",
"lightning": "3.2.20",
"macaroon": "3.0.4",
"morgan": "1.10.0",
"ws": "7.4.4"
}
}
}
},
"load-json-file": {
@ -3713,6 +3842,23 @@
"bolt07": "1.7.1",
"invoices": "1.1.6",
"ln-service": "51.3.1"
},
"dependencies": {
"ln-service": {
"version": "51.3.1",
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-51.3.1.tgz",
"integrity": "sha512-V5Q1rZbN4GY1PfxosQ1pWyyhZF/qPWivf5whL0lDWBrJ4xgsemYRkg5ce4YaUNLYv/BXfMrQVyw09w+tyq+QnQ==",
"requires": {
"bolt07": "1.7.1",
"cors": "2.8.5",
"express": "4.17.1",
"invoices": "1.1.6",
"lightning": "3.2.20",
"macaroon": "3.0.4",
"morgan": "1.10.0",
"ws": "7.4.4"
}
}
}
},
"process-nextick-args": {
@ -5904,9 +6050,9 @@
}
},
"telegraf": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.1.2.tgz",
"integrity": "sha512-YZljmryOhcZgrRWU28AV7UhZ3y+jKRwPwB49PSDDmW7tKtQHrSLBkW0pCoLniDlQVz+ICBGCWXZlHfqDkQ2iNQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.3.0.tgz",
"integrity": "sha512-MuDUtSMipzMzQp8fXbQx76jp4ZD70KREdnpH1idUzN9Zlgm6EzatBQFU4Ps0ipxtEmnBBdghBMumQwrjRe3eqg==",
"requires": {
"abort-controller": "^3.0.0",
"debug": "^4.3.1",
@ -5916,7 +6062,7 @@
"p-timeout": "^4.1.0",
"safe-compare": "^1.1.4",
"sandwich-stream": "^2.0.2",
"typegram": "^3.1.10"
"typegram": "^3.2.0"
},
"dependencies": {
"debug": {
@ -6101,9 +6247,9 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"typegram": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/typegram/-/typegram-3.1.10.tgz",
"integrity": "sha512-VfsuVP4uqk9yllRpaGp9yFpzvLKXghFCOSc3TVi+fy1IL2Bi4z2gJ9rMgVSSug4pWQP8Ui7Isvl14yYV8VEWaQ=="
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/typegram/-/typegram-3.2.3.tgz",
"integrity": "sha512-zlkY7vNTLcwQhLUyYXAUzRelzH752LBFl8m4u04d5g5P7lM9bGegeIRwzd3mVCHJH6R3s48pKeFTVSdVlN+omg=="
},
"typescript": {
"version": "3.9.9",

View file

@ -17,10 +17,11 @@
"asyncjs-util": "1.2.3",
"bitcoin-ops": "1.4.1",
"bitcoinjs-lib": "5.2.0",
"bolt01": "1.2.0",
"bolt03": "1.2.7",
"bolt07": "1.7.1",
"caporal": "1.4.0",
"cbor": "7.0.3",
"cbor": "7.0.4",
"cert-info": "1.5.1",
"colorette": "1.2.2",
"crypto-js": "4.0.0",
@ -32,7 +33,7 @@
"inquirer": "8.0.0",
"invoices": "1.1.6",
"ln-accounting": "4.2.6",
"ln-service": "51.3.1",
"ln-service": "51.5.0",
"ln-sync": "0.4.4",
"ln-telegram": "3.2.1",
"moment": "2.29.1",
@ -43,7 +44,7 @@
"sanitize-filename": "1.6.3",
"stats-lite": "2.2.0",
"table": "6.0.7",
"telegraf": "4.1.2",
"telegraf": "4.3.0",
"update-notifier": "5.1.0",
"window-size": "1.1.1"
},
@ -76,5 +77,5 @@
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
"test": "tap -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/fiat/*.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/wallets/*.js"
},
"version": "8.0.2"
"version": "8.0.3"
}

View file

@ -2,10 +2,12 @@ const advertise = require('./advertise');
const openBalancedChannel = require('./open_balanced_channel');
const purchasePing = require('./purchase_ping');
const serviceKeySendRequests = require('./service_key_send_requests');
const simulateKeySendRequest = require('./simulate_key_send_request');
module.exports = {
advertise,
openBalancedChannel,
purchasePing,
serviceKeySendRequests,
simulateKeySendRequest,
};

View file

@ -0,0 +1,85 @@
const {decodeTlvStream} = require('bolt01');
const base64AsHex = base64 => Buffer.from(base64, 'base64').toString('hex');
const processAfter = () => new Date(Date.now() - (1000*60*60)).toISOString();
/** Derive key send request arguments from invoice details
{
[confirmed_at]: <Settled at ISO 8601 Date String>
description: <Invoice Description String>
[description_hash]: <Invoice Description Hash String>
id: <Payment Hash Hex String>
[is_canceled]: <Invoice is Canceled Bool>
is_confirmed: <Invoice is Confirmed Bool>
payments: [{
is_confirmed: <HTLC is Confirmed Bool>
messages: [{
type: <Message Type String>
value: <Hex Encoded Value String>
}]
}]
received: <Tokens Received Rounded Down Number>
secret: <Invoice Preimage Hex String>
tokens: <Invoiced Tokens Rounded Down Number>
}
@returns
{
[arguments]: {
id: <Payment Hash Hex String>
messages: [{
type: <Message Type String>
value: <Hex Encoded Value String>
}]
received: <Tokens Received Rounded Down Number>
}
}
*/
module.exports = (args) => {
const {id} = args;
// Exit early when there is a simulated keysend invoice
if (!args.is_canceled && args.description_hash === args.secret) {
try {
const encoded = base64AsHex(args.description);
return {
arguments: {
id,
messages: decodeTlvStream({encoded}).records,
received: args.tokens,
},
};
} catch (err) {
return {};
}
}
// Exit early when the invoice is not settled
if (!args.is_confirmed) {
return {};
}
// Exit early when this was confirmed too long ago
if (args.confirmed_at < processAfter()) {
return {};
}
const htlcs = args.payments.filter(n => !!n.is_confirmed);
const {received} = args;
// Exit early when the invoice has no confirmed HTLCs
if (!htlcs.length) {
return {};
}
const [{messages}] = htlcs;
// Exit early when the first HTLC has no messages
if (!messages.length) {
return {};
}
return {arguments: {id, messages, received}};
};

View file

@ -3,7 +3,7 @@ const {formatTokens} = require('ln-sync');
const {getNodeAlias} = require('ln-sync');
const {getPayment} = require('ln-service');
const {parsePaymentRequest} = require('invoices');
const {pay} = require('ln-service');
const {payViaPaymentRequest} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const maximumPingPrice = 200;
@ -15,15 +15,16 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
{
id: <Ping Invoice Id Hex String>
lnd: <Authenticated LND API Object>
lnd: <Server Authenticated LND API Object>
logger: <Winston Logger Object>
pay: <Payer Authenticated LND API Object>
received: <Received Tokens Rounded Down Number>
[request]: <BOLT 11 Encoded Payment Request String>
}
@returns via cbk or Promise
*/
module.exports = ({id, lnd, logger, received, request}, cbk) => {
module.exports = ({id, lnd, logger, pay, received, request}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
@ -40,6 +41,10 @@ module.exports = ({id, lnd, logger, received, request}, cbk) => {
return cbk([400, 'ExpectedWinstonLoggerToRespondToKeySendPing']);
}
if (!pay) {
return cbk([400, 'ExpectedPayerLndToRespondToKeySendPing']);
}
if (received === undefined) {
return cbk([400, 'ExpectedReceivedAmountToRespondToKeySendPing']);
}
@ -68,7 +73,7 @@ module.exports = ({id, lnd, logger, received, request}, cbk) => {
return cbk();
}
return getPayment({lnd, id: parseRequest.id}, (err, res) => {
return getPayment({id: parseRequest.id, lnd: pay}, (err, res) => {
// Ignore errors on payment lookup
if (!!err) {
return cbk();
@ -143,13 +148,15 @@ module.exports = ({id, lnd, logger, received, request}, cbk) => {
// Exit early when the response ping tokens are not as expected
if (parseRequest.mtokens !== tokensAsMillitokens(responsePingTokens)) {
logger.warn({received_bad_ping: 'PingResponseTokensValueIncorrect'});
return cbk();
}
const feeBudget = received - responsePingTokens;
return pay({
lnd,
return payViaPaymentRequest({
request,
lnd: pay,
max_fee: received - responsePingTokens,
},
(err, res) => {

View file

@ -11,18 +11,19 @@ const typePing = '8470534167946609795';
{
id: <Received Payment Invoice Id Hex String>
lnd: <Authenticated LND API Object>
lnd: <Server Authenticated LND API Object>
logger: <Winston Logger Object>
messages: [{
type: <Message Type Number String>
value: <Raw Value Hex String>
}]
pay: <Payer Authenticated LND API Object>
received: <Received Tokens Rounded Down Number>
}
@returns via cbk or Promise
*/
module.exports = ({id, lnd, logger, messages, received}, cbk) => {
module.exports = ({id, lnd, logger, messages, pay, received}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
@ -43,6 +44,10 @@ module.exports = ({id, lnd, logger, messages, received}, cbk) => {
return cbk([400, 'ExpectedMessagesArrayToRespondToKeySendRequest']);
}
if (!pay) {
return cbk([400, 'ExpectedPayerLndToRespondToKeySendRequest']);
}
if (received === undefined) {
return cbk([400, 'ExpectedReceivedTokensRespondToKeySendRequest']);
}
@ -63,6 +68,7 @@ module.exports = ({id, lnd, logger, messages, received}, cbk) => {
id,
lnd,
logger,
pay,
received,
request: hexAsString(ping.value),
},

View file

@ -1,20 +1,26 @@
const asyncAuto = require('async/auto');
const {decodeTlvStream} = require('bolt01');
const {getInvoices} = require('ln-service');
const {getWalletInfo} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {subscribeToInvoices} = require('ln-service');
const keySendRequestArguments = require('./key_send_request_arguments');
const respondToKeySendRequest = require('./respond_to_key_send_request');
const nodeName = (alias, key) => `${alias} ${key}`.trim();
/** Service keysend services requests
{
lnd: <Authenticated LND API Object>
lnd: <Server Authenticated LND API Object>
logger: <Winston Logger Object>
pay: <Payer Authenticated LND API Object>
}
@returns via cbk or Promise
*/
module.exports = ({lnd, logger}, cbk) => {
module.exports = ({lnd, logger, pay}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
@ -27,11 +33,21 @@ module.exports = ({lnd, logger}, cbk) => {
return cbk([400, 'ExpectedLoggerToServiceKeySendRequests']);
}
if (!pay) {
return cbk([400, 'ExpectedPayerLndToServiceKeySendRequests']);
}
return cbk();
},
// Get serving node
getServer: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
// Get payer info
getPayer: ['validate', ({}, cbk) => getWalletInfo({lnd: pay}, cbk)],
// Listen for service requests
listen: ['validate', ({}, cbk) => {
listen: ['getPayer', 'getServer', ({getPayer, getServer}, cbk) => {
const sub = subscribeToInvoices({lnd});
// Stop service when there is an error with the connection
@ -41,38 +57,37 @@ module.exports = ({lnd, logger}, cbk) => {
serving_keysend_requests: [{
type: 'ping',
description: 'Send back ping payment requests with a pong payment',
serving_from: nodeName(getServer.alias, getServer.public_key),
paying_from: nodeName(getPayer.alias, getPayer.public_key),
}],
});
const process = async invoice => {
// Exit early when the invoice is not settled
if (!invoice.is_confirmed) {
return;
}
const {arguments} = keySendRequestArguments({
confirmed_at: invoice.confirmed_at,
description: invoice.description,
description_hash: invoice.description_hash,
id: invoice.id,
is_canceled: invoice.is_canceled,
is_confirmed: invoice.is_confirmed,
payments: invoice.payments,
received: invoice.received,
secret: invoice.secret,
tokens: invoice.tokens,
});
const htlcs = invoice.payments.filter(n => !!n.is_confirmed);
const {id} = invoice;
const {received} = invoice;
// Exit early when the invoice has no confirmed HTLCs
if (!htlcs.length) {
return;
}
// Exit early when the first HTLC has no
const [{messages}] = htlcs;
if (!messages.length) {
if (!arguments) {
return;
}
try {
return await respondToKeySendRequest({
id,
lnd,
logger,
messages,
received,
pay,
id: arguments.id,
messages: arguments.messages,
received: arguments.received,
});
} catch (err) {
return logger.error({err});

View file

@ -0,0 +1,104 @@
const {randomBytes} = require('crypto');
const asyncAuto = require('async/auto');
const {createInvoice} = require('ln-service');
const {encodeTlvStream} = require('bolt01');
const {returnResult} = require('asyncjs-util');
const expiry = () => new Date(Date.now() + (1000 * 60 * 5)).toISOString();
const hexAsBase64 = hex => Buffer.from(hex, 'hex').toString('base64');
const {isArray} = Array;
const isHex = n => !!n && !(n.length % 2) && /^[0-9A-F]*$/i.test(n);
const makeSecret = () => randomBytes(32).toString('hex');
/** Simulate a key send request with an invoice that encodes TLV records
{
lnd: <Authenticated LND API Object>
tokens: <Tokens Received Number>
types: [<Message Type String>]
values: [<Value Hex String>]
}
*/
module.exports = ({lnd, tokens, types, values}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!lnd) {
return cbk([400, 'ExpectedLndToSimulateKeySendRequest']);
}
if (!tokens) {
return cbk([400, 'ExpectedTokensAmountToSimulateKeySendRequest']);
}
if (!isArray(types) || !types.length) {
return cbk([400, 'ExpectedArrayOfTypesToSimulateKeySendRequest']);
}
if (!isArray(values) || !values.length) {
return cbk([400, 'ExpectedArrayOfValuesToSimulateKeySendRequest']);
}
if (types.length !== values.length) {
return cbk([400, 'ExpectedEqualNumbersOfTypesToValues']);
}
return cbk();
},
// Map types and values into message records and encode that as TLV
description: ['validate', ({}, cbk) => {
try {
// Zip together type and value arrays into consolidated records
const records = types.map((type, i) => {
if (!BigInt(type)) {
throw new Error('ExpectedTypeNumberForMessages');
}
if (!isHex(values[i])) {
throw new Error('ExpectedHexValueForMessages');
}
return {type, value: values[i]};
});
// Encode messages as a BOLT 1 TLV stream
const {encoded} = encodeTlvStream({records});
return cbk(null, hexAsBase64(encoded));
} catch (err) {
return cbk([400, 'FailedToEncodeRecordsAsTlvStream', {err}]);
}
}],
// Create simulated key send invoice
createInvoice: ['description', ({description}, cbk) => {
// To indicate this is a false invoice, the secret will be used as the
// description hash, which is not a normal way to create an invoice and
// would be dangerous to do for a public payment request.
const secret = makeSecret();
console.log("CREATE", {
description,
secret,
tokens,
description_hash: secret,
expires_at: expiry(),
})
return createInvoice({
description,
lnd,
secret,
tokens,
description_hash: secret,
expires_at: expiry(),
},
cbk);
}],
},
returnResult({reject, resolve}, cbk));
});
};

View file

@ -362,7 +362,7 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
from: ctx.message.from.id,
id: connectedId,
nodes: getNodes,
reply: n => ctx.replyWithMarkdown(n),
reply: n => ctx.reply(n),
});
} catch (err) {
return logger.error({err});
@ -388,6 +388,7 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
'/liquidity [with] - View node liquidity',
'/mempool - BTC mempool report',
'/pay - Pay an invoice',
'/pending - View pending channels, probes, and forwards',
'/version - View the current bot version',
];