balanceofsatoshis 24.2.2 → 25.0.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Versions
2
2
 
3
+ ## 25.0.0
4
+
5
+ - `pending`: Add command to show pending channels and in-flight HTLCs
6
+
7
+ ### Breaking Changes
8
+
9
+ - Remove `price` command
10
+
3
11
  ## 24.2.2
4
12
 
5
13
  - `open`: Adhere to BIP 69 when ordering funding outputs with equal amounts
package/README.md CHANGED
@@ -147,8 +147,8 @@ bos pay "payment_request"
147
147
  # Show channel-connected peers
148
148
  bos peers
149
149
 
150
- # Output the price of BTC
151
- bos price
150
+ # Show pending channels and in-flight HTLCs
151
+ bos pending
152
152
 
153
153
  # Test if funds can be sent to a destination
154
154
  bos probe "payment_request/public_key"
package/bos CHANGED
@@ -14,7 +14,6 @@ const {writeFile} = require('fs');
14
14
  const importLazy = require('import-lazy')(require);
15
15
 
16
16
  const accounting = importLazy('ln-accounting');
17
- const fiat = importLazy('@alexbosworth/fiat');
18
17
  const lnService = importLazy('ln-service');
19
18
  const lnSync = importLazy('ln-sync');
20
19
  const paidServices = importLazy('paid-services');
@@ -1613,28 +1612,28 @@ prog
1613
1612
  });
1614
1613
  })
1615
1614
 
1616
- // Get the current fiat exchange price
1617
- .command('price', 'Get the price')
1618
- .help('Price is denominated in cents or equivalent')
1619
- .help('Rate provider options: coinbase, coindesk, or coingecko')
1620
- .argument('[symbols...]', 'Desired fiat tickers')
1621
- .option('--file <path>', 'Write the output to a JSON file at desired path')
1622
- .option('--from <provider>', 'Rate provider', priceProviders, 'coindesk')
1615
+ // Get pending channels and in-flight HTLCs
1616
+ .command('pending', 'Show pending channels and in-flight HTLCs')
1617
+ .help('Channels waiting to open or close, forwarding and probing HTLCs')
1618
+ .help('Multiple --node arguments are supported to show items across nodes')
1619
+ .help('Use --verbose to also show the raw details of the pending items')
1620
+ .option('--show-relays <count>', 'Relaying peers to show in paths', INT, 0)
1623
1621
  .option('--no-color', 'Mute all colors')
1622
+ .option('--node <node_name>', 'Node to get pending items for', REPEATABLE)
1624
1623
  .action((args, options, logger) => {
1625
- return new Promise((resolve, reject) => {
1626
- return fiat.getPrices({
1627
- from: options.from,
1628
- request: commands.simpleRequest,
1629
- symbols: args.symbols.map(n => n.toUpperCase()),
1630
- },
1631
- responses.returnObject({
1632
- logger,
1633
- reject,
1634
- resolve,
1635
- file: options.file,
1636
- write: writeFile,
1637
- }));
1624
+ return new Promise(async (resolve, reject) => {
1625
+ try {
1626
+ return network.getPending({
1627
+ logger,
1628
+ fs: {getFile: readFile},
1629
+ is_monochrome: !!options.noColor,
1630
+ lnds: (await lnd.getLnds({logger, nodes: options.node})).lnds,
1631
+ max_relays: options.showRelays,
1632
+ },
1633
+ responses.returnOutput({logger, reject, resolve}));
1634
+ } catch (err) {
1635
+ return logger.error({err}) && reject();
1636
+ }
1638
1637
  });
1639
1638
  })
1640
1639
 
@@ -1,3 +1,6 @@
1
+ const exitCodeSigint = 130;
2
+ const isForceClose = err => !!err && err.name === 'ExitPromptError';
3
+
1
4
  /** Get ask function
2
5
 
3
6
  {}
@@ -9,6 +12,17 @@ module.exports = ({}) => {
9
12
  return new Promise(async resolve => {
10
13
  const inquirer = (await import('inquirer')).default;
11
14
 
12
- return resolve((n, cbk) => inquirer.prompt([n]).then(res => cbk(res)));
15
+ return resolve((n, cbk) => {
16
+ return inquirer.prompt([n])
17
+ .then(res => cbk(res))
18
+ .catch(err => {
19
+ // Exit quietly when the user presses ctrl+c
20
+ if (isForceClose(err)) {
21
+ return process.exit(exitCodeSigint);
22
+ }
23
+
24
+ throw err;
25
+ });
26
+ });
13
27
  });
14
28
  };
@@ -0,0 +1,329 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMap = require('async/map');
3
+ const {getChannels} = require('ln-service');
4
+ const {getNodeAlias} = require('ln-sync');
5
+ const {getPendingChannels} = require('ln-service');
6
+ const {getWalletInfo} = require('ln-service');
7
+ const {returnResult} = require('asyncjs-util');
8
+
9
+ const getPendingPaths = require('./get_pending_paths');
10
+ const pendingPayments = require('./pending_payments');
11
+ const {sortBy} = require('./../arrays');
12
+
13
+ const flatten = arr => [].concat(...arr);
14
+ const lookupTimeoutMs = 1000 * 10;
15
+ const uniq = arr => Array.from(new Set(arr));
16
+
17
+ /** Get pending channels and in-flight HTLCs for a node
18
+
19
+ Items are sorted oldest first, by their creation time when it is known and
20
+ otherwise by the blocks remaining until they resolve
21
+
22
+ Debug details of pending channels, HTLCs and payments are logged in verbose
23
+
24
+ {
25
+ lnd: <Authenticated LND API Object>
26
+ logger: <Winston Logger Object>
27
+ }
28
+
29
+ @returns via cbk or Promise
30
+ {
31
+ closing: [{
32
+ [blocks_until_recovery]: <Blocks Until Pending Balance Recovers Number>
33
+ [close_transaction_id]: <Closing Transaction Id Hex String>
34
+ [peer_alias]: <Peer Alias String>
35
+ peer_public_key: <Peer Public Key Hex String>
36
+ [pending_balance]: <Pending Recovery Tokens Number>
37
+ }]
38
+ forwarding: [{
39
+ fee: <Routing Fee Tokens Number>
40
+ [in_peer_alias]: <Inbound Peer Alias String>
41
+ in_peer_public_key: <Inbound Peer Public Key Hex String>
42
+ [out_peer_alias]: <Outbound Peer Alias String>
43
+ out_peer_public_key: <Outbound Peer Public Key Hex String>
44
+ tokens: <Forwarding Tokens Number>
45
+ }]
46
+ opening: [{
47
+ capacity: <Channel Capacity Tokens Number>
48
+ is_partner_initiated: <Peer Is Opening The Channel Bool>
49
+ [peer_alias]: <Peer Alias String>
50
+ peer_public_key: <Peer Public Key Hex String>
51
+ transaction_id: <Funding Transaction Id Hex String>
52
+ }]
53
+ sending: [{
54
+ [fee]: <Routing Fee Tokens Number>
55
+ hops: [{
56
+ [alias]: <Hop Node Alias String>
57
+ public_key: <Hop Node Public Key Hex String>
58
+ }]
59
+ is_rebalance: <Payment Is Going Back To The Node Itself Bool>
60
+ tokens: <Sending Tokens Number>
61
+ }]
62
+ [alias]: <Node Alias String>
63
+ public_key: <Node Identity Public Key Hex String>
64
+ }
65
+ */
66
+ module.exports = ({lnd, logger}, cbk) => {
67
+ return new Promise((resolve, reject) => {
68
+ return asyncAuto({
69
+ // Check arguments
70
+ validate: cbk => {
71
+ if (!lnd) {
72
+ return cbk([400, 'ExpectedAuthenticatedLndToGetNodePendingItems']);
73
+ }
74
+
75
+ if (!logger) {
76
+ return cbk([400, 'ExpectedLoggerToGetNodePendingItems']);
77
+ }
78
+
79
+ return cbk();
80
+ },
81
+
82
+ // Get channels to look for in-flight HTLCs
83
+ getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
84
+
85
+ // Get the node identity
86
+ getInfo: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
87
+
88
+ // Get pending channels
89
+ getPending: ['validate', ({}, cbk) => getPendingChannels({lnd}, cbk)],
90
+
91
+ // Derive the HTLCs that are forwarding or sending
92
+ htlcs: ['getChannels', ({getChannels}, cbk) => {
93
+ return cbk(null, pendingPayments({channels: getChannels.channels}));
94
+ }],
95
+
96
+ // Look up the in-flight payments behind the outgoing HTLCs
97
+ getPayments: ['htlcs', ({htlcs}, cbk) => {
98
+ return asyncMap(uniq(htlcs.sending.map(n => n.id)), (id, cbk) => {
99
+ return getPendingPaths({id, lnd, timeout_ms: lookupTimeoutMs}, cbk);
100
+ },
101
+ cbk);
102
+ }],
103
+
104
+ // Log the raw details behind the pending items for debugging
105
+ debug: [
106
+ 'getPayments',
107
+ 'getPending',
108
+ 'htlcs',
109
+ ({getPayments, getPending, htlcs}, cbk) =>
110
+ {
111
+ const hasHtlcs = !!htlcs.forwarding.length || !!htlcs.sending.length;
112
+
113
+ // Exit early when there is nothing pending to debug
114
+ if (!hasHtlcs && !getPending.pending_channels.length) {
115
+ return cbk();
116
+ }
117
+
118
+ logger.debug({
119
+ forwarding_htlcs: htlcs.forwarding,
120
+ outgoing_htlcs: htlcs.sending,
121
+ payments: getPayments.map(payment => ({
122
+ id: payment.id,
123
+ created_at: payment.created_at,
124
+ paths: payment.paths.map(path => ({
125
+ hops: path.hops.map(hop => `${hop.channel} ${hop.public_key}`),
126
+ timeout: path.timeout,
127
+ tokens: path.tokens,
128
+ })),
129
+ })),
130
+ pending_channels: getPending.pending_channels.map(channel => ({
131
+ blocks_until_expiry: channel.blocks_until_expiry,
132
+ capacity: channel.capacity,
133
+ close_transaction_id: channel.close_transaction_id,
134
+ is_closing: channel.is_closing,
135
+ is_opening: channel.is_opening,
136
+ is_partner_initiated: channel.is_partner_initiated,
137
+ is_timelocked: channel.is_timelocked,
138
+ local_balance: channel.local_balance,
139
+ partner_public_key: channel.partner_public_key,
140
+ pending_balance: channel.pending_balance,
141
+ pending_payments: channel.pending_payments,
142
+ remote_balance: channel.remote_balance,
143
+ timelock_blocks: channel.timelock_blocks,
144
+ timelock_expiration: channel.timelock_expiration,
145
+ transaction_id: channel.transaction_id,
146
+ transaction_vout: channel.transaction_vout,
147
+ })),
148
+ });
149
+
150
+ return cbk();
151
+ }],
152
+
153
+ // Outgoing HTLCs are matched to the paths of their in-flight payments
154
+ routes: ['getPayments', 'htlcs', ({getPayments, htlcs}, cbk) => {
155
+ const paths = flatten(getPayments.map(payment => {
156
+ return payment.paths.map(path => ({
157
+ path,
158
+ created_at: payment.created_at,
159
+ id: payment.id,
160
+ }));
161
+ }));
162
+
163
+ // Every path can only be the route of a single outgoing HTLC
164
+ const {routes} = htlcs.sending.reduce((sum, htlc) => {
165
+ // An HTLC has the hash, first hop, amount and expiry of its route
166
+ const match = sum.unmatched.find(({id, path}) => {
167
+ const [firstHop] = path.hops;
168
+
169
+ const isSameAmount = path.tokens === htlc.tokens;
170
+ const isSameExpiry = path.timeout === htlc.timeout;
171
+ const isSameHash = id === htlc.id;
172
+ const isSameOutPeer = firstHop.public_key === htlc.out_peer;
173
+
174
+ return isSameHash && isSameAmount && isSameExpiry && isSameOutPeer;
175
+ });
176
+
177
+ // Exit early when there are no payment details for the HTLC
178
+ if (!match) {
179
+ const hop = {forward: htlc.tokens, public_key: htlc.out_peer};
180
+
181
+ return {
182
+ routes: sum.routes.concat({hops: [hop]}),
183
+ unmatched: sum.unmatched,
184
+ };
185
+ }
186
+
187
+ const route = {
188
+ created_at: match.created_at,
189
+ fee: match.path.fee,
190
+ hops: match.path.hops,
191
+ };
192
+
193
+ return {
194
+ routes: sum.routes.concat(route),
195
+ unmatched: sum.unmatched.filter(n => n !== match),
196
+ };
197
+ },
198
+ {routes: [], unmatched: paths});
199
+
200
+ return cbk(null, routes);
201
+ }],
202
+
203
+ // Get the aliases of the peers involved in pending items
204
+ getAliases: [
205
+ 'getPending',
206
+ 'htlcs',
207
+ 'routes',
208
+ ({getPending, htlcs, routes}, cbk) =>
209
+ {
210
+ const hops = flatten(routes.map(n => n.hops));
211
+
212
+ const peers = []
213
+ .concat(getPending.pending_channels.map(n => n.partner_public_key))
214
+ .concat(htlcs.forwarding.map(n => n.in_peer))
215
+ .concat(htlcs.forwarding.map(n => n.out_peer))
216
+ .concat(hops.map(n => n.public_key));
217
+
218
+ return asyncMap(uniq(peers), (id, cbk) => {
219
+ return getNodeAlias({id, lnd}, cbk);
220
+ },
221
+ cbk);
222
+ }],
223
+
224
+ // Assemble the pending items
225
+ pending: [
226
+ 'getAliases',
227
+ 'getInfo',
228
+ 'getPending',
229
+ 'htlcs',
230
+ 'routes',
231
+ ({getAliases, getInfo, getPending, htlcs, routes}, cbk) =>
232
+ {
233
+ const alias = id => {
234
+ return getAliases.find(n => n.id === id).alias || undefined;
235
+ };
236
+
237
+ const pending = getPending.pending_channels;
238
+
239
+ // Closes waiting on a timelock are ordered by their remaining blocks
240
+ const timelocked = sortBy({
241
+ array: pending.filter(n => !!n.is_closing && !!n.is_timelocked),
242
+ attribute: 'timelock_blocks',
243
+ });
244
+
245
+ // Closes that are not timelocked yet are the newest closes
246
+ const waiting = pending.filter(n => n.is_closing && !n.is_timelocked);
247
+
248
+ const closes = [].concat(timelocked.sorted).concat(waiting);
249
+
250
+ // Channels that are closing, which may be waiting on funds recovery
251
+ const closing = closes.map(channel => {
252
+ // A timelock may hold the funds for some blocks before recovery
253
+ const blocks = channel.timelock_blocks || Number();
254
+
255
+ return {
256
+ blocks_until_recovery: blocks > Number() ? blocks : undefined,
257
+ close_transaction_id: channel.close_transaction_id,
258
+ peer_alias: alias(channel.partner_public_key),
259
+ peer_public_key: channel.partner_public_key,
260
+ pending_balance: channel.pending_balance || undefined,
261
+ };
262
+ });
263
+
264
+ // HTLCs that are being relayed from one peer to another
265
+ const forwards = sortBy({
266
+ array: htlcs.forwarding,
267
+ attribute: 'timeout',
268
+ });
269
+
270
+ const forwarding = forwards.sorted.map(forward => ({
271
+ fee: forward.fee,
272
+ in_peer_alias: alias(forward.in_peer),
273
+ in_peer_public_key: forward.in_peer,
274
+ out_peer_alias: alias(forward.out_peer),
275
+ out_peer_public_key: forward.out_peer,
276
+ tokens: forward.tokens,
277
+ }));
278
+
279
+ // Channels that are waiting for funding confirmation
280
+ const opens = sortBy({
281
+ array: pending.filter(n => !!n.is_opening),
282
+ attribute: 'blocks_until_expiry',
283
+ });
284
+
285
+ const opening = opens.sorted.map(channel => ({
286
+ capacity: channel.capacity,
287
+ is_partner_initiated: channel.is_partner_initiated,
288
+ peer_alias: alias(channel.partner_public_key),
289
+ peer_public_key: channel.partner_public_key,
290
+ transaction_id: channel.transaction_id,
291
+ }));
292
+
293
+ // Payments with known creation dates are ordered by their dates
294
+ const dated = sortBy({
295
+ array: routes.filter(n => !!n.created_at),
296
+ attribute: 'created_at',
297
+ });
298
+
299
+ // HTLCs that have no payment details are the newest payments
300
+ const undated = routes.filter(n => !n.created_at);
301
+
302
+ // Payments or probes that are going out through peers
303
+ const sending = [].concat(dated.sorted).concat(undated).map(route => {
304
+ const [finalHop] = route.hops.slice().reverse();
305
+
306
+ return {
307
+ fee: route.fee,
308
+ hops: route.hops.map(hop => ({
309
+ alias: alias(hop.public_key),
310
+ public_key: hop.public_key,
311
+ })),
312
+ is_rebalance: finalHop.public_key === getInfo.public_key,
313
+ tokens: finalHop.forward,
314
+ };
315
+ });
316
+
317
+ return cbk(null, {
318
+ closing,
319
+ forwarding,
320
+ opening,
321
+ sending,
322
+ alias: getInfo.alias || undefined,
323
+ public_key: getInfo.public_key,
324
+ });
325
+ }],
326
+ },
327
+ returnResult({reject, resolve, of: 'pending'}, cbk));
328
+ });
329
+ };
@@ -0,0 +1,81 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMap = require('async/map');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const {getIcons} = require('./../display');
6
+ const getNodePending = require('./get_node_pending');
7
+ const pendingSummary = require('./pending_summary');
8
+
9
+ const {isArray} = Array;
10
+
11
+ /** Get a summary of pending channels and in-flight HTLCs
12
+
13
+ When there are multiple nodes, pending items are grouped by node
14
+
15
+ {
16
+ fs: {
17
+ getFile: <Read File Contents Function> (path, cbk) => {}
18
+ }
19
+ [is_monochrome]: <Mute Colors Bool>
20
+ lnds: [<Authenticated LND API Object>]
21
+ logger: <Winston Logger Object>
22
+ [max_relays]: <Maximum Relaying Peers of a Path to Show Number>
23
+ }
24
+
25
+ @returns via cbk or Promise
26
+ <Pending Items Summary String>
27
+ */
28
+ module.exports = (args, cbk) => {
29
+ return new Promise((resolve, reject) => {
30
+ return asyncAuto({
31
+ // Check arguments
32
+ validate: cbk => {
33
+ if (!args.fs) {
34
+ return cbk([400, 'ExpectedFileSystemMethodsToGetPendingItems']);
35
+ }
36
+
37
+ if (!isArray(args.lnds)) {
38
+ return cbk([400, 'ExpectedArrayOfLndsToGetPendingItems']);
39
+ }
40
+
41
+ if (!args.lnds.length) {
42
+ return cbk([400, 'ExpectedAnLndToGetPendingItems']);
43
+ }
44
+
45
+ if (!args.logger) {
46
+ return cbk([400, 'ExpectedLoggerToGetPendingItems']);
47
+ }
48
+
49
+ if (args.max_relays !== undefined && args.max_relays < 0) {
50
+ return cbk([400, 'ExpectedNonNegativeMaxRelaysCountToShow']);
51
+ }
52
+
53
+ return cbk();
54
+ },
55
+
56
+ // Get the tag icons for nodes
57
+ getIcons: ['validate', ({}, cbk) => getIcons({fs: args.fs}, cbk)],
58
+
59
+ // Get the pending items for every node
60
+ getNodes: ['validate', ({}, cbk) => {
61
+ return asyncMap(args.lnds, (lnd, cbk) => {
62
+ return getNodePending({lnd, logger: args.logger}, cbk);
63
+ },
64
+ cbk);
65
+ }],
66
+
67
+ // Summarize the pending items
68
+ summary: ['getIcons', 'getNodes', ({getIcons, getNodes}, cbk) => {
69
+ const {summary} = pendingSummary({
70
+ is_monochrome: args.is_monochrome,
71
+ max_relays: args.max_relays,
72
+ nodes: getNodes,
73
+ tagged: getIcons.nodes,
74
+ });
75
+
76
+ return cbk(null, summary);
77
+ }],
78
+ },
79
+ returnResult({reject, resolve, of: 'summary'}, cbk));
80
+ });
81
+ };
@@ -0,0 +1,96 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+ const {subscribeToPastPayment} = require('ln-service');
4
+
5
+ const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n);
6
+ const notFound = 404;
7
+
8
+ /** Get the pending paths of an in-flight payment
9
+
10
+ {
11
+ id: <Payment Hash Hex String>
12
+ lnd: <Authenticated LND API Object>
13
+ timeout_ms: <Give Up Waiting For Payment Details After Milliseconds Number>
14
+ }
15
+
16
+ @returns via cbk or Promise
17
+ {
18
+ [created_at]: <Payment Created At ISO 8601 Date String>
19
+ id: <Payment Hash Hex String>
20
+ paths: [{
21
+ fee: <Total Fee Tokens Pending Number>
22
+ hops: [{
23
+ channel: <Standard Format Channel Id String>
24
+ forward: <Forward Tokens Number>
25
+ public_key: <Public Key Hex String>
26
+ }]
27
+ timeout: <Expiration Block Height Number>
28
+ tokens: <Total Tokens Pending Number>
29
+ }]
30
+ }
31
+ */
32
+ module.exports = ({id, lnd, timeout_ms}, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ if (!isHash(id)) {
38
+ return cbk([400, 'ExpectedPaymentHashToGetPendingPaths']);
39
+ }
40
+
41
+ if (!lnd) {
42
+ return cbk([400, 'ExpectedAuthenticatedLndToGetPendingPaths']);
43
+ }
44
+
45
+ if (!timeout_ms) {
46
+ return cbk([400, 'ExpectedTimeoutMillisecondsToGetPendingPaths']);
47
+ }
48
+
49
+ return cbk();
50
+ },
51
+
52
+ // Subscribe to the payment to get its current state
53
+ getPaths: ['validate', ({}, cbk) => {
54
+ const sub = subscribeToPastPayment({id, lnd});
55
+
56
+ // Removing the listeners cancels the subscription to the payment
57
+ const finished = (err, res) => {
58
+ clearTimeout(timeout);
59
+
60
+ sub.removeAllListeners();
61
+
62
+ return cbk(err, res);
63
+ };
64
+
65
+ // Payments that are not in flight have no pending paths
66
+ const noPaths = () => finished(null, {id, paths: []});
67
+
68
+ const timeout = setTimeout(noPaths, timeout_ms);
69
+
70
+ sub.once('confirmed', noPaths);
71
+ sub.once('end', noPaths);
72
+ sub.once('failed', noPaths);
73
+
74
+ sub.once('error', err => {
75
+ // Exit early when the payment is not a known payment
76
+ if (err[0] === notFound) {
77
+ return noPaths();
78
+ }
79
+
80
+ return finished(err);
81
+ });
82
+
83
+ sub.once('paying', pending => {
84
+ return finished(null, {
85
+ id,
86
+ created_at: pending.created_at,
87
+ paths: pending.paths,
88
+ });
89
+ });
90
+
91
+ return;
92
+ }],
93
+ },
94
+ returnResult({reject, resolve, of: 'getPaths'}, cbk));
95
+ });
96
+ };
package/network/index.js CHANGED
@@ -3,6 +3,7 @@ const executeProbe = require('./execute_probe');
3
3
  const getForwards = require('./get_forwards');
4
4
  const getGraphEntry = require('./get_graph_entry');
5
5
  const getPeers = require('./get_peers');
6
+ const getPending = require('./get_pending');
6
7
  const multiPathPayment = require('./multi_path_payment');
7
8
  const multiPathProbe = require('./multi_path_probe');
8
9
  const networks = require('./networks');
@@ -24,6 +25,7 @@ module.exports = {
24
25
  getForwards,
25
26
  getGraphEntry,
26
27
  getPeers,
28
+ getPending,
27
29
  multiPathPayment,
28
30
  multiPathProbe,
29
31
  networks,