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.
- package/CHANGELOG.md +14 -0
- package/bos +8 -1
- package/chain/closing_fees.js +4 -3
- package/chain/fund_transaction.js +16 -13
- package/commands/api.json +4 -0
- package/display/describe_route.js +20 -9
- package/network/blinded_path_fee.js +61 -0
- package/network/execute_probe.js +45 -21
- package/network/pay.js +15 -1
- package/network/probe.js +21 -1
- package/network/probe_destination.js +101 -8
- package/network/push_payment.js +9 -2
- package/offchain/create_invoice.js +155 -7
- package/offchain/get_invoice_amount.js +10 -4
- package/offchain/relay_policy_issue.js +56 -0
- package/offchain/select_blinded_path.js +282 -0
- package/offchain/select_next_hop.js +224 -0
- package/offchain/sign_payment_request.js +46 -4
- package/package.json +34 -8
- package/.dockerignore +0 -5
- package/.gitattributes +0 -1
- package/.prettierignore +0 -2
- package/.tm_properties +0 -1
- package/.travis.yml +0 -15
- package/CONTRIBUTING.md +0 -95
- package/Dockerfile +0 -22
- package/test/arrays/test_shuffle.js +0 -43
- package/test/arrays/test_sort_by.js +0 -47
- package/test/balances/test_balance_from_tokens.js +0 -63
- package/test/balances/test_get_accounting_report.js +0 -127
- package/test/balances/test_get_balance.js +0 -66
- package/test/balances/test_get_liquidity.js +0 -61
- package/test/balances/test_is_matching_filters.js +0 -47
- package/test/balances/test_range_for_date.js +0 -57
- package/test/balances/test_table_rows_from_csv.js +0 -47
- package/test/chain/test_get_chain_fees.js +0 -84
- package/test/chain/test_get_channel_closes.js +0 -41
- package/test/chain/test_get_deposit_address.js +0 -57
- package/test/chain/test_get_mempool_size.js +0 -69
- package/test/chain/test_get_raw_transaction.js +0 -90
- package/test/chain/test_get_utxos.js +0 -137
- package/test/display/test_chart_alias_for_peer.js +0 -43
- package/test/display/test_describe_confidence.js +0 -47
- package/test/display/test_describe_route.js +0 -101
- package/test/display/test_segment_measure.js +0 -30
- package/test/encryption/fixtures.json +0 -4
- package/test/encryption/test_decrypt_ciphertext.js +0 -70
- package/test/encryption/test_der_as_pem.js +0 -32
- package/test/encryption/test_encrypt_to_public_keys.js +0 -75
- package/test/encryption/test_pem_as_der.js +0 -22
- package/test/fixtures/chan_info_response.json +0 -25
- package/test/fixtures/describe_graph_response.json +0 -36
- package/test/fixtures/get_info_response.json +0 -16
- package/test/fixtures/get_node_info_response.json +0 -37
- package/test/fixtures/index.js +0 -23
- package/test/fixtures/liquidity_channels_response.json +0 -68
- package/test/fixtures/list_channels_response.json +0 -47
- package/test/fixtures/list_peers_response.json +0 -16
- package/test/fixtures/pending_channels_response.json +0 -21
- package/test/fixtures/query_routes_response.json +0 -19
- package/test/fixtures/version_info_response.json +0 -6
- package/test/integration/test_fund_transaction.js +0 -38
- package/test/integration/test_open_balanced_channel.js +0 -283
- package/test/integration/test_open_channels.js +0 -120
- package/test/integration/test_remove_peer.js +0 -36
- package/test/integration/test_taproot_channels.js +0 -132
- package/test/integration/test_trusted_channels.js +0 -149
- package/test/integration.js +0 -18
- package/test/lnd/test_credential_restrictions.js +0 -76
- package/test/lnd/test_find_record.js +0 -142
- package/test/lnd/test_get_cert.js +0 -58
- package/test/lnd/test_get_macaroon.js +0 -53
- package/test/lnd/test_get_path.js +0 -57
- package/test/lnd/test_get_socket.js +0 -109
- package/test/lnd/test_lnd_directory.js +0 -81
- package/test/network/test_currency_for_network.js +0 -46
- package/test/network/test_get_peers.js +0 -311
- package/test/network/test_gift_callback_error.js +0 -48
- package/test/network/test_is_relevant_forwards.js +0 -80
- package/test/network/test_is_relevant_source.js +0 -80
- package/test/network/test_multi_probe.js +0 -121
- package/test/network/test_peers_with_activity.js +0 -68
- package/test/network/test_push_payment.js +0 -84
- package/test/network/test_reconnect.js +0 -110
- package/test/network/test_send_gift.js +0 -74
- package/test/network/test_set_autopilot.js +0 -37
- package/test/nodes/test_adjust_tags.js +0 -299
- package/test/nodes/test_delete_node_credentials.js +0 -55
- package/test/nodes/test_get_saved_credentials.js +0 -95
- package/test/nodes/test_get_saved_nodes.js +0 -136
- package/test/nodes/test_put_saved_credentials.js +0 -80
- package/test/nodes/test_register_node.js +0 -331
- package/test/peers/test_channels_from_arguments.js +0 -160
- package/test/peers/test_detect_open_rule_violation.js +0 -78
- package/test/peers/test_find_tag_match.js +0 -143
- package/test/peers/test_open_request_violation.js +0 -70
- package/test/responses/test_return_chart.js +0 -66
- package/test/responses/test_return_json.js +0 -44
- package/test/responses/test_return_number.js +0 -45
- package/test/responses/test_return_object.js +0 -107
- package/test/responses/test_return_output.js +0 -44
- package/test/responses/test_write_json_file.js +0 -59
- package/test/routing/test_channel_for_gift.js +0 -119
- package/test/routing/test_channels_from_hints.js +0 -76
- package/test/routing/test_fees_for_segment.js +0 -47
- package/test/routing/test_forwards_via_peer.js +0 -39
- package/test/routing/test_get_fees_chart.js +0 -111
- package/test/routing/test_get_maximum.js +0 -71
- package/test/routing/test_get_past_forwards.js +0 -61
- package/test/routing/test_gift_route.js +0 -151
- package/test/routing/test_ignore_from_avoid.js +0 -30
- package/test/routing/test_parse_fee_rate_formula.js +0 -69
- package/test/services/test_accept_balanced_channel.js +0 -186
- package/test/services/test_balanced_open_accept_details.js +0 -116
- package/test/services/test_get_balanced_opens.js +0 -174
- package/test/services/test_initiate_balanced_channel.js +0 -332
- package/test/swaps/test_append_failing_edge.js +0 -127
- package/test/swaps/test_channel_for_send.js +0 -50
- package/test/swaps/test_find_rebalance_peer.js +0 -329
- package/test/swaps/test_get_avoid_list.js +0 -62
- package/test/swaps/test_get_paid_service.js +0 -61
- package/test/swaps/test_get_swap_cost.js +0 -135
- package/test/swaps/test_swap_api_key.js +0 -63
- package/test/swaps/test_write_avoid_list.js +0 -83
- package/test/tags/test_get_tags.js +0 -99
- package/test/telegram/test_get_socks_proxy.js +0 -72
- package/test/wallets/test_channels_as_report_activity.js +0 -126
- 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>', '
|
|
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)
|
package/chain/closing_fees.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const {
|
|
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
|
|
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 {
|
|
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 = {
|
|
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 =
|
|
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.
|
|
334
|
+
const spending = tx.inputs.map(input => {
|
|
334
335
|
const outpoint = asOutpoint({
|
|
335
|
-
transaction_id:
|
|
336
|
-
transaction_vout: input.
|
|
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.
|
|
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 /
|
|
356
|
+
fee_tokens_per_vbyte: formattedFeeRate(feeTotal / vsize),
|
|
354
357
|
signed_transaction: sign.transaction,
|
|
355
358
|
});
|
|
356
359
|
}],
|
package/commands/api.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
118
|
-
const
|
|
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
|
|
128
|
+
const delta = !i || isBlinded ? null : prior.timeout - hop.timeout;
|
|
129
|
+
const expiry = delta > 240 ? redBright(` Expiry +${delta}`) : '';
|
|
123
130
|
|
|
124
|
-
|
|
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 =
|
|
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 +
|
|
142
|
+
return [`${gray(channel + edge)}`];
|
|
132
143
|
} else {
|
|
133
|
-
return [`${gray(channel +
|
|
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
|
+
};
|
package/network/execute_probe.js
CHANGED
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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: '
|
|
457
|
+
returnResult({reject, resolve, of: 'result'}, cbk));
|
|
438
458
|
});
|
|
439
459
|
};
|