balanceofsatoshis 23.1.12 → 23.3.0

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bos +8 -1
  3. package/chain/closing_fees.js +4 -3
  4. package/chain/fund_transaction.js +16 -13
  5. package/commands/api.json +4 -0
  6. package/display/describe_route.js +20 -9
  7. package/network/blinded_path_fee.js +61 -0
  8. package/network/execute_probe.js +45 -21
  9. package/network/pay.js +15 -1
  10. package/network/probe.js +21 -1
  11. package/network/probe_destination.js +101 -8
  12. package/network/push_payment.js +9 -2
  13. package/offchain/create_invoice.js +155 -7
  14. package/offchain/get_invoice_amount.js +10 -4
  15. package/offchain/relay_policy_issue.js +56 -0
  16. package/offchain/select_blinded_path.js +282 -0
  17. package/offchain/select_next_hop.js +224 -0
  18. package/offchain/sign_payment_request.js +46 -4
  19. package/package.json +34 -8
  20. package/.dockerignore +0 -5
  21. package/.gitattributes +0 -1
  22. package/.prettierignore +0 -2
  23. package/.tm_properties +0 -1
  24. package/.travis.yml +0 -15
  25. package/CONTRIBUTING.md +0 -95
  26. package/Dockerfile +0 -22
  27. package/test/arrays/test_shuffle.js +0 -43
  28. package/test/arrays/test_sort_by.js +0 -47
  29. package/test/balances/test_balance_from_tokens.js +0 -63
  30. package/test/balances/test_get_accounting_report.js +0 -127
  31. package/test/balances/test_get_balance.js +0 -66
  32. package/test/balances/test_get_liquidity.js +0 -61
  33. package/test/balances/test_is_matching_filters.js +0 -47
  34. package/test/balances/test_range_for_date.js +0 -57
  35. package/test/balances/test_table_rows_from_csv.js +0 -47
  36. package/test/chain/test_get_chain_fees.js +0 -84
  37. package/test/chain/test_get_channel_closes.js +0 -41
  38. package/test/chain/test_get_deposit_address.js +0 -57
  39. package/test/chain/test_get_mempool_size.js +0 -69
  40. package/test/chain/test_get_raw_transaction.js +0 -90
  41. package/test/chain/test_get_utxos.js +0 -137
  42. package/test/display/test_chart_alias_for_peer.js +0 -43
  43. package/test/display/test_describe_confidence.js +0 -47
  44. package/test/display/test_describe_route.js +0 -101
  45. package/test/display/test_segment_measure.js +0 -30
  46. package/test/encryption/fixtures.json +0 -4
  47. package/test/encryption/test_decrypt_ciphertext.js +0 -70
  48. package/test/encryption/test_der_as_pem.js +0 -32
  49. package/test/encryption/test_encrypt_to_public_keys.js +0 -75
  50. package/test/encryption/test_pem_as_der.js +0 -22
  51. package/test/fixtures/chan_info_response.json +0 -25
  52. package/test/fixtures/describe_graph_response.json +0 -36
  53. package/test/fixtures/get_info_response.json +0 -16
  54. package/test/fixtures/get_node_info_response.json +0 -37
  55. package/test/fixtures/index.js +0 -23
  56. package/test/fixtures/liquidity_channels_response.json +0 -68
  57. package/test/fixtures/list_channels_response.json +0 -47
  58. package/test/fixtures/list_peers_response.json +0 -16
  59. package/test/fixtures/pending_channels_response.json +0 -21
  60. package/test/fixtures/query_routes_response.json +0 -19
  61. package/test/fixtures/version_info_response.json +0 -6
  62. package/test/integration/test_fund_transaction.js +0 -38
  63. package/test/integration/test_open_balanced_channel.js +0 -283
  64. package/test/integration/test_open_channels.js +0 -120
  65. package/test/integration/test_remove_peer.js +0 -36
  66. package/test/integration/test_taproot_channels.js +0 -132
  67. package/test/integration/test_trusted_channels.js +0 -149
  68. package/test/integration.js +0 -18
  69. package/test/lnd/test_credential_restrictions.js +0 -76
  70. package/test/lnd/test_find_record.js +0 -142
  71. package/test/lnd/test_get_cert.js +0 -58
  72. package/test/lnd/test_get_macaroon.js +0 -53
  73. package/test/lnd/test_get_path.js +0 -57
  74. package/test/lnd/test_get_socket.js +0 -109
  75. package/test/lnd/test_lnd_directory.js +0 -81
  76. package/test/network/test_currency_for_network.js +0 -46
  77. package/test/network/test_get_peers.js +0 -311
  78. package/test/network/test_gift_callback_error.js +0 -48
  79. package/test/network/test_is_relevant_forwards.js +0 -80
  80. package/test/network/test_is_relevant_source.js +0 -80
  81. package/test/network/test_multi_probe.js +0 -121
  82. package/test/network/test_peers_with_activity.js +0 -68
  83. package/test/network/test_push_payment.js +0 -84
  84. package/test/network/test_reconnect.js +0 -110
  85. package/test/network/test_send_gift.js +0 -74
  86. package/test/network/test_set_autopilot.js +0 -37
  87. package/test/nodes/test_adjust_tags.js +0 -299
  88. package/test/nodes/test_delete_node_credentials.js +0 -55
  89. package/test/nodes/test_get_saved_credentials.js +0 -95
  90. package/test/nodes/test_get_saved_nodes.js +0 -136
  91. package/test/nodes/test_put_saved_credentials.js +0 -80
  92. package/test/nodes/test_register_node.js +0 -331
  93. package/test/peers/test_channels_from_arguments.js +0 -160
  94. package/test/peers/test_detect_open_rule_violation.js +0 -78
  95. package/test/peers/test_find_tag_match.js +0 -143
  96. package/test/peers/test_open_request_violation.js +0 -70
  97. package/test/responses/test_return_chart.js +0 -66
  98. package/test/responses/test_return_json.js +0 -44
  99. package/test/responses/test_return_number.js +0 -45
  100. package/test/responses/test_return_object.js +0 -107
  101. package/test/responses/test_return_output.js +0 -44
  102. package/test/responses/test_write_json_file.js +0 -59
  103. package/test/routing/test_channel_for_gift.js +0 -119
  104. package/test/routing/test_channels_from_hints.js +0 -76
  105. package/test/routing/test_fees_for_segment.js +0 -47
  106. package/test/routing/test_forwards_via_peer.js +0 -39
  107. package/test/routing/test_get_fees_chart.js +0 -111
  108. package/test/routing/test_get_maximum.js +0 -71
  109. package/test/routing/test_get_past_forwards.js +0 -61
  110. package/test/routing/test_gift_route.js +0 -151
  111. package/test/routing/test_ignore_from_avoid.js +0 -30
  112. package/test/routing/test_parse_fee_rate_formula.js +0 -69
  113. package/test/services/test_accept_balanced_channel.js +0 -186
  114. package/test/services/test_balanced_open_accept_details.js +0 -116
  115. package/test/services/test_get_balanced_opens.js +0 -174
  116. package/test/services/test_initiate_balanced_channel.js +0 -332
  117. package/test/swaps/test_append_failing_edge.js +0 -127
  118. package/test/swaps/test_channel_for_send.js +0 -50
  119. package/test/swaps/test_find_rebalance_peer.js +0 -329
  120. package/test/swaps/test_get_avoid_list.js +0 -62
  121. package/test/swaps/test_get_paid_service.js +0 -61
  122. package/test/swaps/test_get_swap_cost.js +0 -135
  123. package/test/swaps/test_swap_api_key.js +0 -63
  124. package/test/swaps/test_write_avoid_list.js +0 -83
  125. package/test/tags/test_get_tags.js +0 -99
  126. package/test/telegram/test_get_socks_proxy.js +0 -72
  127. package/test/wallets/test_channels_as_report_activity.js +0 -126
  128. package/test/wallets/test_report_overview.js +0 -73
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Versions
2
2
 
3
+ ## 23.3.0
4
+
5
+ - `call`: Add `subscribeToMessages` to listen for received onion messages
6
+
7
+ ## 23.2.0
8
+
9
+ - `invoice`: Add `--encrypted-hints` to hide the node behind blinded paths in
10
+ the payment request
11
+ - `invoice`: Add `--select-hints` with `--encrypted-hints` to choose the
12
+ blinded path outwards from a peer to an introduction node
13
+ - `invoice`: Add `--virtual-fee-rate` with selected `--encrypted-hints` to
14
+ charge a receiver fee on the blinded path
15
+ - `pay`, `probe`: Add support for payment requests with blinded paths
16
+
3
17
  ## 23.1.12
4
18
 
5
19
  - Add support for LND 0.21.3
package/bos CHANGED
@@ -1147,7 +1147,11 @@ prog
1147
1147
  .help('--virtual invoices cannot be used with payers who probe before pay')
1148
1148
  .help('Only one --virtual invoice can be active at a time')
1149
1149
  .help('--reject-on-amount-increase can only be used with --virtual invoices')
1150
+ .help('--encrypted-hints hides the node with blinded paths, needs an amount')
1151
+ .help('--encrypted-hints with --select-hints picks the path outwards by hop')
1152
+ .help('--virtual-fee-rate with selected --encrypted-hints is a receiver fee')
1150
1153
  .argument('[amount]', 'Amount for invoice', STRING, '0')
1154
+ .option('--encrypted-hints', 'Use blinded paths instead of hop hints')
1151
1155
  .option('--for <description>', 'What is the invoice requesting payment for')
1152
1156
  .option('--hours <expires_in>', 'Hours invoice is valid for', INT)
1153
1157
  .option('--include-hints', 'Include the default set of hop hint channels')
@@ -1157,7 +1161,7 @@ prog
1157
1161
  .option('--reject-on-amount-increase', 'Reject if required amount increases')
1158
1162
  .option('--select-hints', 'Select hop hints to be added to the request')
1159
1163
  .option('--virtual', 'Request payment over a virtual channel')
1160
- .option('--virtual-fee-rate <pm>', 'Fee rate to use on virtual channel', INT)
1164
+ .option('--virtual-fee-rate <pm>', 'Receiver fee rate on virtual/path', INT)
1161
1165
  .action((args, options, logger) => {
1162
1166
  return new Promise(async (resolve, reject) => {
1163
1167
  try {
@@ -1167,6 +1171,7 @@ prog
1167
1171
  ask: await commands.interrogate({}),
1168
1172
  description: options.for,
1169
1173
  expires_in: options.hours,
1174
+ is_encrypting_hints: options.encryptedHints || undefined,
1170
1175
  is_hinting: options.includeHints || undefined,
1171
1176
  is_including_qr: !!options.qr || undefined,
1172
1177
  is_rejecting_option: !!options.rejectOnAmountIncrease || undefined,
@@ -1551,6 +1556,7 @@ prog
1551
1556
 
1552
1557
  // Pay a payment request, probing first
1553
1558
  .command('pay', 'Pay a payment request, probing first')
1559
+ .help('Blinded path requests are probed to the introduction node, 1 path')
1554
1560
  .argument('<request>', 'Payment Request')
1555
1561
  .option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
1556
1562
  .option('--avoid-high-fee-routes', 'Avoid evaluating too-high fee routes')
@@ -1670,6 +1676,7 @@ prog
1670
1676
  // Determine if a payment request is sendable
1671
1677
  .command('probe', 'Check if a payment request is sendable')
1672
1678
  .help('Simulate paying a payment request without actually paying it')
1679
+ .help('Blinded path requests are probed to the introduction node, 1 path')
1673
1680
  .argument('<to>', 'Payment request or node public key')
1674
1681
  .argument('[amount]', 'Amount to probe, default: request amount')
1675
1682
  .option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
@@ -1,6 +1,5 @@
1
- const {Transaction} = require('bitcoinjs-lib');
1
+ const {componentsOfTransaction} = require('@alexbosworth/blockchain');
2
2
 
3
- const {fromHex} = Transaction;
4
3
  const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
5
4
 
6
5
  /** Calculate channel closing fees paid
@@ -40,7 +39,9 @@ module.exports = args => {
40
39
  }
41
40
 
42
41
  // Calculate the value of the outputs in the tx that spends the capacity
43
- const outValue = sumOf(fromHex(closer.transaction).outs.map(n => n.value));
42
+ const {outputs} = componentsOfTransaction({transaction: closer.transaction});
43
+
44
+ const outValue = sumOf(outputs.map(n => n.tokens));
44
45
 
45
46
  const fees = []
46
47
  .concat(args.capacity - outValue)
@@ -1,6 +1,7 @@
1
1
  const asyncAuto = require('async/auto');
2
2
  const asyncEach = require('async/each');
3
3
  const {broadcastTransaction} = require('ln-sync');
4
+ const {componentsOfTransaction} = require('@alexbosworth/blockchain');
4
5
  const {formatTokens} = require('ln-sync');
5
6
  const {fundPsbt} = require('ln-service');
6
7
  const {getChainFeeRate} = require('ln-service');
@@ -10,7 +11,7 @@ const {getUtxos} = require('ln-service');
10
11
  const {parseAmount} = require('ln-accounting');
11
12
  const {returnResult} = require('asyncjs-util');
12
13
  const {signPsbt} = require('ln-service');
13
- const {Transaction} = require('bitcoinjs-lib');
14
+ const {sizeOfTransaction} = require('@alexbosworth/blockchain');
14
15
  const {unlockUtxo} = require('ln-service');
15
16
 
16
17
  const allowUnconfirmed = 0;
@@ -18,19 +19,14 @@ const asBigUnit = n => (n / 1e8).toFixed(8);
18
19
  const asOutpoint = utxo => `${utxo.transaction_id}:${utxo.transaction_vout}`;
19
20
  const asInput = n => ({transaction_id: n.id, transaction_vout: n.vout});
20
21
  const asUtxo = n => ({id: n.slice(0, 64), vout: Number(n.slice(65))});
21
- const bufferAsHex = buffer => buffer.toString('hex');
22
22
  const dustValue = 293;
23
23
  const formattedFeeRate = n => n.toFixed(2);
24
- const {fromHex} = Transaction;
25
24
  const hasMaxAmount = amounts => !!amounts.find(n => !!n && !!/max/gim.test(n));
26
25
  const {isArray} = Array;
27
26
  const isOutpoint = n => !!n && /^[0-9A-F]{64}:[0-9]{1,6}$/i.test(n);
28
27
  const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
29
28
  const isValidFeeRate = n => !n || Number.isInteger(Number(n));
30
- const minConfs = 1;
31
29
  const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
32
- const taprootAddressVersion = 1;
33
- const txHashAsTxId = hash => hash.reverse().toString('hex');
34
30
 
35
31
  /** Fund and sign a transaction
36
32
 
@@ -246,7 +242,12 @@ module.exports = (args, cbk) => {
246
242
  try {
247
243
  const outputs = args.addresses.map((address, i) => {
248
244
  const amount = args.amounts[i];
249
- const variables = {max: getMax.max_tokens};
245
+ const variables = {};
246
+
247
+ // The max variable is only defined when a maximum is calculated
248
+ if (getMax.max_tokens !== undefined) {
249
+ variables.max = getMax.max_tokens;
250
+ }
250
251
 
251
252
  return {address, tokens: parseAmount({amount, variables}).tokens};
252
253
  });
@@ -327,13 +328,13 @@ module.exports = (args, cbk) => {
327
328
  // Final funded transaction
328
329
  funded: ['getUtxos', 'sign', ({getUtxos, sign}, cbk) => {
329
330
  // Match the inputs of the tx up to the wallet outputs
330
- const tx = fromHex(sign.transaction);
331
+ const tx = componentsOfTransaction({transaction: sign.transaction});
331
332
 
332
333
  // Find the UTXOs that are being spent in the final transaction
333
- const spending = tx.ins.map(input => {
334
+ const spending = tx.inputs.map(input => {
334
335
  const outpoint = asOutpoint({
335
- transaction_id: txHashAsTxId(input.hash),
336
- transaction_vout: input.index,
336
+ transaction_id: input.id,
337
+ transaction_vout: input.vout,
337
338
  });
338
339
 
339
340
  return getUtxos.utxos.find(n => asOutpoint(n) === outpoint);
@@ -345,12 +346,14 @@ module.exports = (args, cbk) => {
345
346
  }
346
347
 
347
348
  const inputsValue = sumOf(spending.map(n => n.tokens));
348
- const outputsValue = sumOf(tx.outs.map(n => n.value));
349
+ const outputsValue = sumOf(tx.outputs.map(n => n.tokens));
349
350
 
350
351
  const feeTotal = inputsValue - outputsValue;
351
352
 
353
+ const {vsize} = sizeOfTransaction({transaction: sign.transaction});
354
+
352
355
  return cbk(null, {
353
- fee_tokens_per_vbyte: formattedFeeRate(feeTotal / tx.virtualSize()),
356
+ fee_tokens_per_vbyte: formattedFeeRate(feeTotal / vsize),
354
357
  signed_transaction: sign.transaction,
355
358
  });
356
359
  }],
package/commands/api.json CHANGED
@@ -971,6 +971,10 @@
971
971
  "events": ["invoice_updated"],
972
972
  "method": "subscribeToInvoices"
973
973
  },
974
+ {
975
+ "events": ["message_received"],
976
+ "method": "subscribeToMessages"
977
+ },
974
978
  {
975
979
  "arguments": [{
976
980
  "description": "Payment hash id hex-encoded",
@@ -10,8 +10,10 @@ const describeConfidence = require('./describe_confidence');
10
10
  const formatFeeRate = require('./format_fee_rate');
11
11
 
12
12
  const aliasColor = n => !!n ? greenBright(n) : '';
13
+ const blindedChannel = 'blinded';
13
14
  const effectiveFeeRate = (n, m) => Number(BigInt(1e6) * BigInt(n) / BigInt(m));
14
15
  const flatten = arr => [].concat(...arr);
16
+ const isBlindedHop = hop => !!hop.encrypted_data && !hop.path_key;
15
17
  const isEmoji = /(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F/g;
16
18
  const pairEdgeIndex = (pair, key) => `x${Number(!pair.indexOf(key))}`;
17
19
 
@@ -26,10 +28,12 @@ const pairEdgeIndex = (pair, key) => `x${Number(!pair.indexOf(key))}`;
26
28
  hops: [{
27
29
  channel: <Standard Format Channel Id String>
28
30
  channel_capacity: <Channel Capacity Tokens Number>
31
+ [encrypted_data]: <Blinded Path Encrypted Data Hex String>
29
32
  fee: <Fee Number>
30
33
  fee_mtokens: <Fee Millitokens String>
31
34
  forward: <Forward Tokens Number>
32
35
  forward_mtokens: <Forward Millitokens String>
36
+ [path_key]: <Blinded Path Key Hex String>
33
37
  public_key: <Public Key Hex String>
34
38
  timeout: <Timeout Block Height Number>
35
39
  }]
@@ -109,28 +113,35 @@ module.exports = ({lnd, route, tagged}, cbk) => {
109
113
 
110
114
  const path = route.hops.map((hop, i, hops) => {
111
115
  const {alias} = getAliases.find(n => n.id === hop.public_key);
112
- const {channel} = hop;
113
- const delta = !i ? null : hops[i - 1].timeout - hops[i].timeout;
116
+ const isBlinded = isBlindedHop(hop);
114
117
  const isFinal = i === hops.length - 1 && hops.length !== 1;
115
118
  const pair = [(hops[i - 1] || {}).public_key, hop.public_key].sort();
116
119
 
117
- const edgeIndex = pairEdgeIndex(pair, hop.public_key);
118
- const expiry = delta > 240 ? redBright(` Expiry +${delta}`) : '';
120
+ // Blinded hops have no channel, timeout or amount of their own
121
+ const channel = !isBlinded ? hop.channel : blindedChannel;
122
+ const edge = isBlinded ? '' : pairEdgeIndex(pair, hop.public_key);
119
123
  const feeMtokens = isFinal ? hops[i-1].fee_mtokens : hop.fee_mtokens;
120
124
  const forwarder = `${aliasColor(alias)} ${hop.public_key}`.trim();
125
+ const hasForward = !!BigInt(hop.forward_mtokens || Number());
126
+ const prior = hops[i - 1];
121
127
 
122
- const feeRate = effectiveFeeRate(feeMtokens, hop.forward_mtokens);
128
+ const delta = !i || isBlinded ? null : prior.timeout - hop.timeout;
129
+ const expiry = delta > 240 ? redBright(` Expiry +${delta}`) : '';
123
130
 
124
- const rate = formatFeeRate({rate: feeRate}).display;
131
+ // The rate is unknown when the hop forward amount is unknown
132
+ const rate = !hasForward ? null : formatFeeRate({
133
+ rate: effectiveFeeRate(feeMtokens, hop.forward_mtokens),
134
+ }).display;
125
135
 
126
- const forward = `${forwarder}. Fee rate: ${rate}${expiry}`;
136
+ const forward = rate === null ? forwarder :
137
+ `${forwarder}. Fee rate: ${rate}${expiry}`;
127
138
 
128
139
  if (!i) {
129
140
  return [`${gray(channel)} ${description || String()}`, forward];
130
141
  } else if (i === hops.length - [i].length) {
131
- return [`${gray(channel + edgeIndex)}`];
142
+ return [`${gray(channel + edge)}`];
132
143
  } else {
133
- return [`${gray(channel + edgeIndex)}`, forward];
144
+ return [`${gray(channel + edge)}`, forward];
134
145
  }
135
146
  });
136
147
 
@@ -0,0 +1,61 @@
1
+ const feeRateDenominator = BigInt(1e6);
2
+ const {isArray} = Array;
3
+ const isMtokens = n => typeof n === 'string' && /^\d+$/.test(n);
4
+ const isNumber = n => typeof n === 'number' && !isNaN(n);
5
+ const noFee = '0';
6
+
7
+ /** Calculate the fee to deliver an amount through a blinded path
8
+
9
+ {
10
+ mtokens: <Millitokens To Deliver To The Destination String>
11
+ path: {
12
+ base_fee_mtokens: <Accumulated Base Fee Millitokens String>
13
+ fee_rate: <Accumulated Fee Rate Millitokens Per Million Number>
14
+ hops: [{
15
+ encrypted_data: <Encrypted Recipient Data Hex String>
16
+ relay_key: <Relaying Node Public Key Hex String>
17
+ }]
18
+ }
19
+ }
20
+
21
+ @throws
22
+ <Error>
23
+
24
+ @returns
25
+ {
26
+ fee_mtokens: <Blinded Path Fee Millitokens String>
27
+ }
28
+ */
29
+ module.exports = ({mtokens, path}) => {
30
+ if (!isMtokens(mtokens)) {
31
+ throw new Error('ExpectedMillitokensToCalculateBlindedPathFee');
32
+ }
33
+
34
+ if (!path) {
35
+ throw new Error('ExpectedBlindedPathToCalculateBlindedPathFee');
36
+ }
37
+
38
+ if (!isMtokens(path.base_fee_mtokens)) {
39
+ throw new Error('ExpectedBaseFeeMillitokensToCalculateBlindedPathFee');
40
+ }
41
+
42
+ if (!isNumber(path.fee_rate)) {
43
+ throw new Error('ExpectedFeeRateToCalculateBlindedPathFee');
44
+ }
45
+
46
+ if (!isArray(path.hops) || !path.hops.length) {
47
+ throw new Error('ExpectedPathHopsToCalculateBlindedPathFee');
48
+ }
49
+
50
+ // Exit early when the path is just the destination, there is no relaying
51
+ if (path.hops.length === [path].length) {
52
+ return {fee_mtokens: noFee};
53
+ }
54
+
55
+ const baseFee = BigInt(path.base_fee_mtokens);
56
+ const proportional = BigInt(mtokens) * BigInt(path.fee_rate);
57
+
58
+ const fee = baseFee + proportional / feeRateDenominator;
59
+
60
+ return {fee_mtokens: fee.toString()};
61
+ };
@@ -16,9 +16,11 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
16
16
 
17
17
  /** Execute a probe
18
18
 
19
+ `destination` is not required when probing to blinded `paths`
20
+
19
21
  {
20
22
  cltv_delta: <Final Cltv Delta Number>
21
- destination: <Final Destination Public Key Hex String>
23
+ [destination]: <Final Destination Public Key Hex String>
22
24
  [features]: [{
23
25
  bit: <Feature Bit Number>
24
26
  }]
@@ -39,6 +41,19 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
39
41
  value: <Message To Final Destination Raw Value Hex Encoded String>
40
42
  }]
41
43
  [outgoing_channel]: <Outgoing Channel Id String>
44
+ [paths]: [{
45
+ base_fee_mtokens: <Accumulated Base Fee Millitokens String>
46
+ cltv_delta: <Accumulated CLTV Expiry Delta Number>
47
+ fee_rate: <Accumulated Fee Rate Millitokens Per Million Number>
48
+ hops: [{
49
+ encrypted_data: <Encrypted Recipient Data Hex String>
50
+ relay_key: <Relaying Node Public Key Hex String>
51
+ }]
52
+ [introduction_node]: <Introduction Node Public Key Hex String>
53
+ key: <First Hop Path Key Public Key Hex String>
54
+ [max_htlc_mtokens]: <Maximum HTLC Millitokens String>
55
+ [min_htlc_mtokens]: <Minimum HTLC Millitokens String>
56
+ }]
42
57
  [payment]: <Payment Identifier Hex String>
43
58
  [routes]: [[{
44
59
  [base_fee_mtokens]: <Base Fee Millitokens String>
@@ -67,10 +82,12 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
67
82
  hops: [{
68
83
  channel: <Standard Format Channel Id String>
69
84
  channel_capacity: <Channel Capacity Tokens Number>
85
+ [encrypted_data]: <Blinded Path Encrypted Data Hex String>
70
86
  fee: <Fee Number>
71
87
  fee_mtokens: <Fee Millitokens String>
72
88
  forward: <Forward Tokens Number>
73
89
  forward_mtokens: <Forward Millitokens String>
90
+ [path_key]: <Blinded Path Key Hex String>
74
91
  public_key: <Public Key Hex String>
75
92
  timeout: <Timeout Block Height Number>
76
93
  }]
@@ -89,7 +106,7 @@ module.exports = (args, cbk) => {
89
106
  return cbk([400, 'ExpectedFinalCltvDeltaToExecuteProbe']);
90
107
  }
91
108
 
92
- if (!args.destination) {
109
+ if (!args.destination && !args.paths) {
93
110
  return cbk([400, 'ExpectedDestinationToExecuteProbe']);
94
111
  }
95
112
 
@@ -145,25 +162,32 @@ module.exports = (args, cbk) => {
145
162
 
146
163
  const timeoutMinutes = minutesAsMs((args.timeout_minutes || Number()));
147
164
 
148
- const sub = subscribeToProbeForRoute({
149
- mtokens,
150
- cltv_delta: args.cltv_delta,
151
- destination: args.destination,
152
- features: args.features,
153
- ignore: args.ignore,
154
- incoming_peer: args.in_through,
155
- lnd: args.lnd,
156
- max_fee_mtokens: strictMaxFee || undefined,
157
- max_timeout_height: args.max_timeout_height,
158
- messages: args.messages,
159
- outgoing_channel: args.outgoing_channel,
160
- path_timeout_ms: pathTimeoutMs,
161
- payment: args.payment,
162
- probe_timeout_ms: timeoutMinutes || undefined,
163
- routes: args.routes,
164
- tokens: args.tokens,
165
- total_mtokens: !!args.payment ? mtokens : undefined,
166
- });
165
+ let sub;
166
+
167
+ try {
168
+ sub = subscribeToProbeForRoute({
169
+ mtokens,
170
+ cltv_delta: args.cltv_delta,
171
+ destination: args.destination,
172
+ features: args.features,
173
+ ignore: args.ignore,
174
+ incoming_peer: args.in_through,
175
+ lnd: args.lnd,
176
+ max_fee_mtokens: strictMaxFee || undefined,
177
+ max_timeout_height: args.max_timeout_height,
178
+ messages: args.messages,
179
+ outgoing_channel: args.outgoing_channel,
180
+ path_timeout_ms: pathTimeoutMs,
181
+ paths: args.paths,
182
+ payment: args.payment,
183
+ probe_timeout_ms: timeoutMinutes || undefined,
184
+ routes: args.routes,
185
+ tokens: args.tokens,
186
+ total_mtokens: !!args.payment ? mtokens : undefined,
187
+ });
188
+ } catch (err) {
189
+ return cbk([400, 'FailedToStartProbeForRoute', {err}]);
190
+ }
167
191
 
168
192
  const finished = (err, res) => {
169
193
  sub.removeAllListeners();
package/network/pay.js CHANGED
@@ -21,6 +21,7 @@ const {isArray} = Array;
21
21
  const mtokensAsTokens = mtokens => Number(mtokens / BigInt(1e3));
22
22
  const pathTimeoutMs = 1000 * 60 * 5;
23
23
  const singlePath = 1;
24
+ const withTotalFee = ({fee, ...res}) => ({total_fee: fee, ...res});
24
25
 
25
26
  /** Make a payment
26
27
 
@@ -229,6 +230,11 @@ module.exports = (args, cbk) => {
229
230
  const paths = [];
230
231
  const request = parsePaymentRequest({request: args.request});
231
232
 
233
+ // Multi-path payments look for paths to a destination, not to paths
234
+ if (!!request.paths) {
235
+ return cbk([501, 'MultiPathPayNotSupportedWithBlindedPaths']);
236
+ }
237
+
232
238
  const sub = subscribeToMultiPathProbe({
233
239
  cltv_delta: request.cltv_delta,
234
240
  destination: request.destination,
@@ -399,7 +405,15 @@ module.exports = (args, cbk) => {
399
405
  'singlePathPay',
400
406
  ({multiPathPay, singlePathPay}, cbk) =>
401
407
  {
402
- return cbk(null, singlePathPay || multiPathPay);
408
+ const payment = singlePathPay || multiPathPay;
409
+
410
+ // Exit early when the payment was not through a blinded path
411
+ if (!payment || payment.blinded_path_fee === undefined) {
412
+ return cbk(null, payment);
413
+ }
414
+
415
+ // The fee of a blinded path payment is a total including the path fee
416
+ return cbk(null, withTotalFee(payment));
403
417
  }],
404
418
  },
405
419
  returnResult({reject, resolve, of: 'payment'}, cbk));
package/network/probe.js CHANGED
@@ -30,6 +30,7 @@ const pathTimeoutMs = 1000 * 60 * 90;
30
30
  const singlePath = 1;
31
31
  const uniq = arr => Array.from(new Set(arr));
32
32
  const unsupported = 501;
33
+ const withTotalFee = ({fee, ...res}) => ({total_fee: fee, ...res});
33
34
 
34
35
  /** Probe a destination, looking for multiple non-overlapping paths
35
36
 
@@ -54,10 +55,12 @@ const unsupported = 501;
54
55
 
55
56
  @returns via cbk or Promise
56
57
  {
58
+ [blinded_path_fee]: <Blinded Path Fee Tokens Number>
57
59
  [fee]: <Total Fee Tokens To Destination Number>
58
60
  [latency_ms]: <Latency Milliseconds Number>
59
61
  [relays]: [[<Relaying Public Key Hex String>]]
60
62
  [routes_maximum]: <Maximum Sendable Tokens on Paths Number>
63
+ [total_fee]: <Total Fee Tokens Including Blinded Path Fee Number>
61
64
  }
62
65
  */
63
66
  module.exports = (args, cbk) => {
@@ -109,6 +112,7 @@ module.exports = (args, cbk) => {
109
112
  cltv_delta: decoded.cltv_delta,
110
113
  destination: decoded.destination,
111
114
  features: decoded.features,
115
+ paths: decoded.paths,
112
116
  routes: decoded.routes,
113
117
  });
114
118
  }],
@@ -319,6 +323,11 @@ module.exports = (args, cbk) => {
319
323
  return cbk([501, 'StrictMaxFeeNotSupportedWithMultiPathProbes']);
320
324
  }
321
325
 
326
+ // Multi-path probing looks for paths to a destination, not to paths
327
+ if (!!decodeRequest.paths) {
328
+ return cbk([501, 'MultiPathProbeNotSupportedWithBlindedPaths']);
329
+ }
330
+
322
331
  // Exit with error when the backing LND is below 0.10.0
323
332
  if (!!isLegacy) {
324
333
  return cbk([501, 'BackingLndDoesNotSupportMultiPathPayments']);
@@ -433,7 +442,18 @@ module.exports = (args, cbk) => {
433
442
 
434
443
  return cbk();
435
444
  }],
445
+
446
+ // Final probe result
447
+ result: ['checkFee', 'probe', ({probe}, cbk) => {
448
+ // Exit early when the probe was not through a blinded path
449
+ if (!probe || probe.blinded_path_fee === undefined) {
450
+ return cbk(null, probe);
451
+ }
452
+
453
+ // The fee of a blinded path probe is a total that includes the path fee
454
+ return cbk(null, withTotalFee(probe));
455
+ }],
436
456
  },
437
- returnResult({reject, resolve, of: 'probe'}, cbk));
457
+ returnResult({reject, resolve, of: 'result'}, cbk));
438
458
  });
439
459
  };