balanceofsatoshis 11.47.2 → 11.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Versions
2
2
 
3
+ ## 11.49.1
4
+
5
+ - `telegram`: Add support for notifying of new opening channels
6
+
7
+ ## 11.48.1
8
+
9
+ - `fund`: Support sending to P2TR addresses
10
+
3
11
  ## 11.47.2
4
12
 
5
13
  - `telegram`: Correct open and close channel messages not posting
package/CONTRIBUTING.md CHANGED
@@ -17,6 +17,8 @@ If you want to help with style, here are some rough guidelines on style ideas:
17
17
  - Minimize function nesting, make new files if nesting is required
18
18
  - No extraneous whitespace
19
19
  - A single newline should appear at the end of a file
20
+ - A single line should contain max 80 characters including the newline
21
+ - If a top level scalar would go over 80 characters in a line, that's ok
20
22
  - Don't bother with () in functions when not needed: `const a = b => c`
21
23
  - If there are multiple things together, alphabetize them
22
24
  - Don't split up long strings over multiple lines
@@ -27,11 +29,14 @@ If you want to help with style, here are some rough guidelines on style ideas:
27
29
  - Use single '' quotes not double "" quotes, except when `` is required
28
30
  - If conditions should avoid spanning multiple lines
29
31
  - Avoid double specifying an attribute and value, `{type: type}` vs `{type}`
32
+ - Always use {} with if, else, etc statements
33
+ - Document the top of methods with what the arguments are and what is returned
30
34
 
31
35
  ### Control Flow
32
36
 
33
37
  - Async functions should support both cbk and Promise style
34
38
  - Use async.js methods for asynchronous control flow
39
+ - Use async auto and returnResult for all non-event async functions
35
40
  - Try to exit early from functions when possible, and note this exit in comment
36
41
  - Prefer cbk over Promise style, aside from in tests or in Promise libs
37
42
  - Use `asyncAuto` for asynchronous control flow dependency management
@@ -49,6 +54,8 @@ If you want to help with style, here are some rough guidelines on style ideas:
49
54
  - Short properties always go first in objects: `{short, longer: type}`
50
55
  - If a statement relies on a statement above it, it should have a newline above
51
56
  - Avoid including scalar values such as strings or numbers in the code itself
57
+ - Prefer hex serialization over base64 or Buffers in arguments or output
58
+ - Avoid any function arguments that are multiple data types
52
59
 
53
60
  ### JS Features
54
61
 
@@ -60,6 +67,9 @@ If you want to help with style, here are some rough guidelines on style ideas:
60
67
  - Try to avoid passing objects in arguments as much as possible
61
68
  - Prefer using function iteration like map and forEach over for and while
62
69
  - Use arrow functions and not `function` functions whenever possible
70
+ - Functions should only take and return a single object as argument, result
71
+ - Use the || flags to select between truthy variable options
72
+ - Use !! to explicitly coerce non Bool variables to Boolean
63
73
 
64
74
  ### Errors
65
75
 
@@ -70,3 +80,5 @@ If you want to help with style, here are some rough guidelines on style ideas:
70
80
  - Use HTTP status codes as a guideline: 4** is a local issue, 5** is remote
71
81
  - Return async errors as arrays: `[typeNumber, errorMsgString, extraDetails]`
72
82
  - Try to be very specific with error messages and try to not repeat one
83
+ - Callbacks should always be called as (err, result)
84
+ - Try and catch should not be used unless an error is expected
@@ -1,10 +1,15 @@
1
+ const {address} = require('bitcoinjs-lib');
1
2
  const asyncAuto = require('async/auto');
2
3
  const asyncEach = require('async/each');
4
+ const {createPsbt} = require('psbt');
3
5
  const {formatTokens} = require('ln-sync');
6
+ const {fromBech32} = address;
4
7
  const {fundPsbt} = require('ln-service');
5
8
  const {getChainFeeRate} = require('ln-service');
6
9
  const {getMaxFundAmount} = require('ln-sync');
10
+ const {getNetwork} = require('ln-sync');
7
11
  const {getUtxos} = require('ln-service');
12
+ const {networks} = require('bitcoinjs-lib');
8
13
  const {returnResult} = require('asyncjs-util');
9
14
  const {signPsbt} = require('ln-service');
10
15
  const {Transaction} = require('bitcoinjs-lib');
@@ -16,6 +21,7 @@ const asBigUnit = n => (n / 1e8).toFixed(8);
16
21
  const asOutpoint = utxo => `${utxo.transaction_id}:${utxo.transaction_vout}`;
17
22
  const asInput = n => ({transaction_id: n.id, transaction_vout: n.vout});
18
23
  const asUtxo = n => ({id: n.slice(0, 64), vout: Number(n.slice(65))});
24
+ const bufferAsHex = buffer => buffer.toString('hex');
19
25
  const dustValue = 293;
20
26
  const formattedFeeRate = n => n.toFixed(2);
21
27
  const {fromHex} = Transaction;
@@ -25,6 +31,8 @@ const isOutpoint = n => !!n && /^[0-9A-F]{64}:[0-9]{1,6}$/i.test(n);
25
31
  const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
26
32
  const minConfs = 1;
27
33
  const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
34
+ const taprootAddressVersion = 1;
35
+ const {toOutputScript} = address;
28
36
  const txHashAsTxId = hash => hash.reverse().toString('hex');
29
37
 
30
38
  /** Fund and sign a transaction
@@ -98,6 +106,9 @@ module.exports = (args, cbk) => {
98
106
  // Get the current fee rate
99
107
  getFee: ['validate', ({}, cbk) => getChainFeeRate({lnd: args.lnd}, cbk)],
100
108
 
109
+ // Get the network name
110
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
111
+
101
112
  // Derive a list of outputs to guide input selection
102
113
  outputs: ['validate', ({}, cbk) => {
103
114
  // Exit early when the amount is open ended and thus depends on inputs
@@ -241,8 +252,9 @@ module.exports = (args, cbk) => {
241
252
  fund: [
242
253
  'finalOutputs',
243
254
  'getFee',
255
+ 'getNetwork',
244
256
  'utxos',
245
- ({finalOutputs, getFee, utxos}, cbk) =>
257
+ ({finalOutputs, getFee, getNetwork, utxos}, cbk) =>
246
258
  {
247
259
  const inputs = utxos.map(asUtxo).map(asInput);
248
260
  const feeRate = args.fee_tokens_per_vbyte || getFee.tokens_per_vbyte;
@@ -258,11 +270,50 @@ module.exports = (args, cbk) => {
258
270
  requested_fee_rate: feeRate,
259
271
  });
260
272
 
273
+ const hasTaprootOutput = !!finalOutputs.find(n => {
274
+ try {
275
+ return fromBech32(n.address).version === taprootAddressVersion;
276
+ } catch (err) {
277
+ return false;
278
+ }
279
+ });
280
+
281
+ // Exit early when there is no taproot output
282
+ if (!hasTaprootOutput) {
283
+ return fundPsbt({
284
+ fee_tokens_per_vbyte: feeRate,
285
+ inputs: !!inputs.length ? inputs : undefined,
286
+ lnd: args.lnd,
287
+ outputs: finalOutputs,
288
+ },
289
+ cbk);
290
+ }
291
+
292
+ const network = networks[getNetwork.bitcoinjs];
293
+
294
+ if (!network) {
295
+ return cbk([400, 'UnsupportedNetworkForFundingOutputs']);
296
+ }
297
+
298
+ const warn = console.warn;
299
+
300
+ console.warn = () => {};
301
+
302
+ const {psbt} = createPsbt({
303
+ outputs: finalOutputs.map(({address, tokens}) => ({
304
+ tokens,
305
+ script: bufferAsHex(toOutputScript(address, network)),
306
+ })),
307
+ utxos: inputs.map(input => ({
308
+ id: input.transaction_id,
309
+ vout: input.transaction_vout,
310
+ })),
311
+ });
312
+
261
313
  return fundPsbt({
314
+ psbt,
262
315
  fee_tokens_per_vbyte: feeRate,
263
- inputs: !!inputs.length ? inputs : undefined,
264
316
  lnd: args.lnd,
265
- outputs: finalOutputs,
266
317
  },
267
318
  cbk);
268
319
  }],
package/package.json CHANGED
@@ -27,21 +27,21 @@
27
27
  "colorette": "2.0.16",
28
28
  "crypto-js": "4.1.1",
29
29
  "csv-parse": "5.0.4",
30
- "goldengate": "11.0.0",
30
+ "goldengate": "11.0.1",
31
31
  "grammy": "1.7.0",
32
32
  "hot-formula-parser": "4.0.0",
33
33
  "import-lazy": "4.0.0",
34
34
  "ini": "2.0.0",
35
35
  "inquirer": "8.2.0",
36
- "invoices": "2.0.3",
36
+ "invoices": "2.0.4",
37
37
  "ln-accounting": "5.0.5",
38
- "ln-service": "53.7.1",
39
- "ln-sync": "3.9.0",
40
- "ln-telegram": "3.14.1",
38
+ "ln-service": "53.7.3",
39
+ "ln-sync": "3.10.0",
40
+ "ln-telegram": "3.15.1",
41
41
  "moment": "2.29.1",
42
42
  "paid-services": "3.11.0",
43
- "probing": "2.0.2",
44
- "psbt": "1.1.11",
43
+ "probing": "2.0.3",
44
+ "psbt": "2.0.0",
45
45
  "qrcode-terminal": "0.12.0",
46
46
  "sanitize-filename": "1.6.3",
47
47
  "socks-proxy-agent": "6.1.1",
@@ -52,9 +52,9 @@
52
52
  "description": "Lightning balance CLI",
53
53
  "devDependencies": {
54
54
  "@alexbosworth/tap": "15.0.10",
55
- "ln-docker-daemons": "2.2.3",
55
+ "ln-docker-daemons": "2.2.4",
56
56
  "mock-lnd": "1.4.1",
57
- "secp256k1": "4.0.3"
57
+ "tiny-secp256k1": "2.2.0"
58
58
  },
59
59
  "engines": {
60
60
  "node": ">=12.20"
@@ -81,5 +81,5 @@
81
81
  "postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
82
82
  "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/wallets/*.js"
83
83
  },
84
- "version": "11.47.2"
84
+ "version": "11.49.1"
85
85
  }
@@ -11,7 +11,6 @@ const asyncMap = require('async/map');
11
11
  const asyncReflect = require('async/reflect');
12
12
  const asyncRetry = require('async/retry');
13
13
  const {cancelPendingChannel} = require('ln-service');
14
- const {decodePsbt} = require('psbt');
15
14
  const {fundPendingChannels} = require('ln-service');
16
15
  const {getFundedTransaction} = require('ln-sync');
17
16
  const {getNetwork} = require('ln-sync');
@@ -24,7 +23,6 @@ const {maintainUtxoLocks} = require('ln-sync');
24
23
  const moment = require('moment');
25
24
  const {returnResult} = require('asyncjs-util');
26
25
  const {Transaction} = require('bitcoinjs-lib');
27
- const {transactionAsPsbt} = require('psbt');
28
26
  const {unlockUtxo} = require('ln-service');
29
27
 
30
28
  const adjustFees = require('./../routing/adjust_fees');
@@ -657,19 +655,7 @@ module.exports = (args, cbk) => {
657
655
  'setFeeRates',
658
656
  ({getFunding, fundingPsbt}, cbk) =>
659
657
  {
660
- try {
661
- const tx = getFunding.value.transaction;
662
-
663
- const decoded = decodePsbt({psbt: fundingPsbt.value.psbt});
664
-
665
- const transaction = tx || decoded.unsigned_transaction;
666
-
667
- return cbk(null, {
668
- transaction_id: Transaction.fromHex(transaction).getId(),
669
- });
670
- } catch (err) {
671
- return cbk([503, 'UnexpectedErrorGettingTransactionId', {err}]);
672
- }
658
+ return cbk(null, {transaction_id: getFunding.value.id});
673
659
  }],
674
660
  },
675
661
  returnResult({reject, resolve, of: 'completed'}, cbk));
@@ -36,6 +36,7 @@ const {postChainTransaction} = require('ln-telegram');
36
36
  const {postClosedMessage} = require('ln-telegram');
37
37
  const {postCreatedTrade} = require('ln-telegram');
38
38
  const {postOpenMessage} = require('ln-telegram');
39
+ const {postOpeningMessage} = require('ln-telegram');
39
40
  const {postSettledInvoice} = require('ln-telegram');
40
41
  const {postSettledPayment} = require('ln-telegram');
41
42
  const {postSettledTrade} = require('ln-telegram');
@@ -49,6 +50,7 @@ const {subscribeToBlocks} = require('goldengate');
49
50
  const {subscribeToChannels} = require('ln-service');
50
51
  const {subscribeToInvoices} = require('ln-service');
51
52
  const {subscribeToPastPayments} = require('ln-service');
53
+ const {subscribeToPendingChannels} = require('ln-sync');
52
54
  const {subscribeToTransactions} = require('ln-service');
53
55
 
54
56
  const interaction = require('./interaction');
@@ -299,7 +301,7 @@ module.exports = (args, cbk) => {
299
301
  key: apiKey.key,
300
302
  nodes: allNodes,
301
303
  reply: ctx.reply,
302
- send: file => ctx.replyWithDocument(fileAsDoc(file)),
304
+ send: (file, opts) => ctx.replyWithDocument(fileAsDoc(file), opts),
303
305
  },
304
306
  err => !!err && !!err[0] >= 500 ? logger.error({err}) : null);
305
307
 
@@ -675,6 +677,36 @@ module.exports = (args, cbk) => {
675
677
  cbk);
676
678
  }],
677
679
 
680
+ // Pending channels changes
681
+ pending: ['apiKey', 'getNodes', 'userId', ({getNodes, userId}, cbk) => {
682
+ return asyncEach(getNodes, ({from, lnd}, cbk) => {
683
+ const sub = subscribeToPendingChannels({lnd});
684
+
685
+ subscriptions.push(sub);
686
+
687
+ sub.on('opening', update => {
688
+ return postOpeningMessage({
689
+ from,
690
+ lnd,
691
+ id: connectedId,
692
+ opening: update.channels,
693
+ send: (id, msg, opt) => bot.api.sendMessage(id, msg, opt),
694
+ },
695
+ err => !!err ? logger.error({node: from, pend_err: err}) : null);
696
+ });
697
+
698
+ sub.once('error', err => {
699
+ // Terminate subscription and restart after a delay
700
+ sub.removeAllListeners();
701
+
702
+ return cbk([503, 'UnexpectedErrorInPendingSubscription', {err}]);
703
+ });
704
+
705
+ return;
706
+ },
707
+ cbk);
708
+ }],
709
+
678
710
  // Send connected message
679
711
  connected: [
680
712
  'apiKey',
@@ -748,16 +780,14 @@ module.exports = (args, cbk) => {
748
780
 
749
781
  // Subscribe to invoices
750
782
  invoices: ['apiKey', 'getNodes', 'userId', ({apiKey, getNodes}, cbk) => {
751
- return asyncEach(getNodes, ({from, lnd}, cbk) => {
752
- const sub = subscribeToInvoices({lnd});
783
+ return asyncEach(getNodes, (node, cbk) => {
784
+ const sub = subscribeToInvoices({lnd: node.lnd});
753
785
 
754
786
  subscriptions.push(sub);
755
787
 
756
788
  sub.on('invoice_updated', invoice => {
757
789
  return postSettledInvoice({
758
- from,
759
- lnd,
760
- request,
790
+ from: node.from,
761
791
  id: connectedId,
762
792
  invoice: {
763
793
  description: invoice.description,
@@ -766,7 +796,9 @@ module.exports = (args, cbk) => {
766
796
  payments: invoice.payments,
767
797
  received: invoice.received,
768
798
  },
769
- key: apiKey.key,
799
+ key: node.public_key,
800
+ lnd: node.lnd,
801
+ nodes: getNodes,
770
802
  quiz: ({answers, correct, question}) => {
771
803
  return bot.api.sendQuiz(
772
804
  connectedId,
@@ -775,6 +807,7 @@ module.exports = (args, cbk) => {
775
807
  {correct_option_id: correct},
776
808
  );
777
809
  },
810
+ send: (id, msg, opts) => bot.api.sendMessage(id, msg, opts),
778
811
  },
779
812
  err => !!err ? logger.error({settled_err: err}) : null);
780
813
  });
@@ -0,0 +1,61 @@
1
+ const {address} = require('bitcoinjs-lib');
2
+ const {crypto} = require('bitcoinjs-lib');
3
+ const {networks} = require('bitcoinjs-lib');
4
+ const {script} = require('bitcoinjs-lib');
5
+ const {spawnLightningCluster} = require('ln-docker-daemons');
6
+ const {test} = require('@alexbosworth/tap');
7
+ const tinysecp = require('tiny-secp256k1');
8
+
9
+ const {fundTransaction} = require('./../../chain');
10
+
11
+ const {compile} = script;
12
+ const count = 100;
13
+ const {fromOutputScript} = address;
14
+ const makeTaprootKey = (k, h) => tinysecp.xOnlyPointAddTweak(k, h).xOnlyPubkey;
15
+ const OP_1 = 81;
16
+ const shortKey = keyPair => keyPair.publicKey.slice(1, 33);
17
+ const tapHash = k => crypto.taggedHash('TapTweak', k.publicKey.slice(1, 33));
18
+ const tokens = 1e6;
19
+
20
+ // Funding a transaction should result in a funded tx
21
+ test(`Fund transaction`, async ({end, equal, strictSame}) => {
22
+ const ecp = (await import('ecpair')).ECPairFactory(tinysecp);
23
+ const {kill, nodes} = await spawnLightningCluster({});
24
+
25
+ const [{generate, lnd}] = nodes;
26
+
27
+ await generate({count});
28
+
29
+ const keyPair = ecp.makeRandom({network: networks.regtest});
30
+
31
+ const outputKey = makeTaprootKey(shortKey(keyPair), tapHash(keyPair));
32
+ const tweakHash = tapHash(keyPair);
33
+
34
+ const outputScript = compile([OP_1, Buffer.from(outputKey)]);
35
+
36
+ // Suppress fromOutputScript warning on Taproot address
37
+ const warn = console.warn;
38
+ console.warn = () => {};
39
+
40
+ const address = fromOutputScript(outputScript, networks.regtest);
41
+
42
+ // Restore warnings
43
+ console.warn = warn;
44
+
45
+ try {
46
+ await fundTransaction({
47
+ lnd,
48
+ addresses: [address],
49
+ amounts: [tokens.toString()],
50
+ ask: () => {},
51
+ spend: [],
52
+ is_dry_run: false,
53
+ logger: {error: () => {}, info: () => {}},
54
+ utxos: [],
55
+ });
56
+ } catch (err) {
57
+ equal(err, null, 'Expected no error');
58
+ }
59
+
60
+ await kill({});
61
+ });
@@ -2,11 +2,13 @@ const EventEmitter = require('events');
2
2
 
3
3
  const {createSignedRequest} = require('invoices');
4
4
  const {createUnsignedRequest} = require('invoices');
5
- const sign = require('secp256k1').ecdsaSign;
5
+ const secp256k1 = require('tiny-secp256k1');
6
6
  const {test} = require('@alexbosworth/tap');
7
7
 
8
8
  const getBalancedOpens = require('./../../services/get_balanced_opens');
9
9
 
10
+ const sign = (h, k) => Buffer.from(secp256k1.sign(h, k));
11
+
10
12
  const createRequest = () => {
11
13
  const {hash, hrp, tags} = createUnsignedRequest({
12
14
  created_at: '2017-06-01T10:57:38.000Z',
@@ -32,7 +34,7 @@ const createRequest = () => {
32
34
  const bufFromHex = hex => Buffer.from(hex, 'hex');
33
35
  const privateKey = 'e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734';
34
36
 
35
- const {signature} = sign(bufFromHex(hash), bufFromHex(privateKey));
37
+ const signature = sign(bufFromHex(hash), bufFromHex(privateKey));
36
38
 
37
39
  const {request} = createSignedRequest({
38
40
  hrp,