balanceofsatoshis 24.1.0 → 24.2.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 +5 -0
- package/README.md +2 -2
- package/bos +14 -3
- package/commands/fetch_request.js +1 -3
- package/network/execute_probe.js +2 -2
- package/network/get_out_peers.js +235 -0
- package/network/multi_path_probe.js +1 -1
- package/network/pay.js +17 -17
- package/network/probe.js +15 -18
- package/network/probe_destination.js +26 -7
- package/network/push_payment.js +1 -1
- package/network/remove_peer.js +8 -5
- package/network/transfer_funds.js +2 -2
- package/package.json +2 -4
- package/swaps/get_routes_for_funding.js +3 -1
- package/swaps/rebalance.js +1 -1
- package/swaps/swap_api_key.js +1 -1
- package/swaps/swap_out.js +1 -1
- package/chain/transaction_records.js +0 -183
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
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 fetch = importLazy('@alexbosworth/node-fetch');
|
|
18
17
|
const fiat = importLazy('@alexbosworth/fiat');
|
|
19
18
|
const lnService = importLazy('ln-service');
|
|
20
19
|
const lnSync = importLazy('ln-sync');
|
|
@@ -1517,6 +1516,10 @@ prog
|
|
|
1517
1516
|
// Pay a payment request, probing first
|
|
1518
1517
|
.command('pay', 'Pay a payment request, probing first')
|
|
1519
1518
|
.help('Blinded path requests are probed to the introduction node, 1 path')
|
|
1519
|
+
.help('--out can take a tag to make the first hop through any tagged peer')
|
|
1520
|
+
.help('--out-filter vars: CAPACITY/HEIGHTS/INBOUND_LIQUIDITY')
|
|
1521
|
+
.help('--out-filter vars: OUTBOUND_LIQUIDITY/PENDING_PAYMENTS')
|
|
1522
|
+
.help('--out-filter vars: INBOUND_BASE_FEE/INBOUND_FEE_RATE')
|
|
1520
1523
|
.argument('<request>', 'Payment Request')
|
|
1521
1524
|
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
|
1522
1525
|
.option('--avoid-high-fee-routes', 'Avoid evaluating too-high fee routes')
|
|
@@ -1526,7 +1529,8 @@ prog
|
|
|
1526
1529
|
.option('--message <message>', 'Attach text message to payment')
|
|
1527
1530
|
.option('--no-color', 'Mute all colors')
|
|
1528
1531
|
.option('--node <node_name>', 'Node to use to pay payment request')
|
|
1529
|
-
.option('--out <
|
|
1532
|
+
.option('--out <peer_or_tag>', 'Make first hop through peer/tag', REPEATABLE)
|
|
1533
|
+
.option('--out-filter <out_filter>', 'Filter outbound peer/tag', REPEATABLE)
|
|
1530
1534
|
.action((args, options, logger) => {
|
|
1531
1535
|
return new Promise(async (resolve, reject) => {
|
|
1532
1536
|
try {
|
|
@@ -1545,6 +1549,7 @@ prog
|
|
|
1545
1549
|
max_paths: options.maxPaths,
|
|
1546
1550
|
message: options.message,
|
|
1547
1551
|
out: flatten([options.out].filter(n => !!n)),
|
|
1552
|
+
out_filters: flatten([options.outFilter].filter(n => !!n)),
|
|
1548
1553
|
request: args.request,
|
|
1549
1554
|
},
|
|
1550
1555
|
responses.returnObject({logger, reject, resolve}));
|
|
@@ -1637,6 +1642,10 @@ prog
|
|
|
1637
1642
|
.command('probe', 'Check if a payment request is sendable')
|
|
1638
1643
|
.help('Simulate paying a payment request without actually paying it')
|
|
1639
1644
|
.help('Blinded path requests are probed to the introduction node, 1 path')
|
|
1645
|
+
.help('--out can take a tag to make the first hop through any tagged peer')
|
|
1646
|
+
.help('--out-filter vars: CAPACITY/HEIGHTS/INBOUND_LIQUIDITY')
|
|
1647
|
+
.help('--out-filter vars: OUTBOUND_LIQUIDITY/PENDING_PAYMENTS')
|
|
1648
|
+
.help('--out-filter vars: INBOUND_BASE_FEE/INBOUND_FEE_RATE')
|
|
1640
1649
|
.argument('<to>', 'Payment request or node public key')
|
|
1641
1650
|
.argument('[amount]', 'Amount to probe, default: request amount')
|
|
1642
1651
|
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
|
@@ -1647,7 +1656,8 @@ prog
|
|
|
1647
1656
|
.option('--max-paths <max>', 'Maximum paths to use for find-max', INT, 1)
|
|
1648
1657
|
.option('--no-color', 'Mute all colors')
|
|
1649
1658
|
.option('--node <node_name>', 'Node to use for payment request check')
|
|
1650
|
-
.option('--out <
|
|
1659
|
+
.option('--out <peer_or_tag>', 'Make first hop through peer/tag', REPEATABLE)
|
|
1660
|
+
.option('--out-filter <out_filter>', 'Filter outbound peer/tag', REPEATABLE)
|
|
1651
1661
|
.action((args, options, logger) => {
|
|
1652
1662
|
return new Promise(async (resolve, reject) => {
|
|
1653
1663
|
try {
|
|
@@ -1667,6 +1677,7 @@ prog
|
|
|
1667
1677
|
max_fee: options.maxFee,
|
|
1668
1678
|
max_paths: options.maxPaths,
|
|
1669
1679
|
out: flatten([options.out].filter(n => !!n)),
|
|
1680
|
+
out_filters: flatten([options.outFilter].filter(n => !!n)),
|
|
1670
1681
|
request: args.to.length !== 66 ? args.to : undefined,
|
|
1671
1682
|
tokens: args.amount || undefined,
|
|
1672
1683
|
},
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
const {stringify} = require('querystring');
|
|
2
2
|
|
|
3
|
-
const {AbortController} = require('abort-controller');
|
|
4
|
-
|
|
5
3
|
const encodeQs = qs => !qs ? '' : '?' + stringify(qs);
|
|
6
4
|
const timeoutSignals = new WeakMap();
|
|
7
5
|
|
|
8
6
|
/** Derive a request function that uses fetch to simulate request behavior
|
|
9
7
|
|
|
10
8
|
{
|
|
11
|
-
fetch: <
|
|
9
|
+
fetch: <Fetch Function>
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
@returns
|
package/network/execute_probe.js
CHANGED
|
@@ -40,7 +40,7 @@ const tokensAsMillitokens = tok => (BigInt(tok) * BigInt(1e3)).toString();
|
|
|
40
40
|
type: <Message To Final Destination Type Number String>
|
|
41
41
|
value: <Message To Final Destination Raw Value Hex Encoded String>
|
|
42
42
|
}]
|
|
43
|
-
[
|
|
43
|
+
[outgoing_channels]: [<Outgoing Channel Id String>]
|
|
44
44
|
[paths]: [{
|
|
45
45
|
base_fee_mtokens: <Accumulated Base Fee Millitokens String>
|
|
46
46
|
cltv_delta: <Accumulated CLTV Expiry Delta Number>
|
|
@@ -176,7 +176,7 @@ module.exports = (args, cbk) => {
|
|
|
176
176
|
max_fee_mtokens: strictMaxFee || undefined,
|
|
177
177
|
max_timeout_height: args.max_timeout_height,
|
|
178
178
|
messages: args.messages,
|
|
179
|
-
|
|
179
|
+
outgoing_channels: args.outgoing_channels,
|
|
180
180
|
path_timeout_ms: pathTimeoutMs,
|
|
181
181
|
paths: args.paths,
|
|
182
182
|
payment: args.payment,
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncMapSeries = require('async/mapSeries');
|
|
3
|
+
const {findKey} = require('ln-sync');
|
|
4
|
+
const {getChannels} = require('ln-service');
|
|
5
|
+
const {getIdentity} = require('ln-service');
|
|
6
|
+
const {getNode} = require('ln-service');
|
|
7
|
+
const {returnResult} = require('asyncjs-util');
|
|
8
|
+
|
|
9
|
+
const findPeerMatch = require('./../peers/find_peer_match');
|
|
10
|
+
const {getTags} = require('./../tags');
|
|
11
|
+
|
|
12
|
+
const flatten = arr => [].concat(...arr);
|
|
13
|
+
const {isArray} = Array;
|
|
14
|
+
const isFeeFilter = n => /\binbound_(base_fee|fee_rate)\b/i.test(n);
|
|
15
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
16
|
+
|
|
17
|
+
/** Get the peers to route out through, expanding tags and applying filters
|
|
18
|
+
|
|
19
|
+
A tag query expands to all of the tagged peers that satisfy the filters
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
[filters]: [<Outbound Peer Filter Formula String>]
|
|
23
|
+
[fs]: {
|
|
24
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
25
|
+
}
|
|
26
|
+
lnd: <Authenticated LND API Object>
|
|
27
|
+
out: [<Out Through Peer Public Key, Alias, or Tag String>]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@returns via cbk or Promise
|
|
31
|
+
{
|
|
32
|
+
public_keys: [<Out Through Peer Public Key Hex String>]
|
|
33
|
+
}
|
|
34
|
+
*/
|
|
35
|
+
module.exports = (args, cbk) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
return asyncAuto({
|
|
38
|
+
// Check arguments
|
|
39
|
+
validate: cbk => {
|
|
40
|
+
if (!!args.filters && !isArray(args.filters)) {
|
|
41
|
+
return cbk([400, 'ExpectedArrayOfOutFiltersToGetOutPeers']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!args.lnd) {
|
|
45
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetOutPeers']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isArray(args.out)) {
|
|
49
|
+
return cbk([400, 'ExpectedArrayOfOutQueriesToGetOutPeers']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!!args.out.filter(n => !n).length) {
|
|
53
|
+
return cbk([400, 'ExpectedNonEmptyOutQueriesToGetOutPeers']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Filters narrow down out peers so there must be out peers to filter
|
|
57
|
+
if (!args.out.length && !!args.filters && !!args.filters.length) {
|
|
58
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return cbk();
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Get channels to find peers
|
|
65
|
+
getChannels: ['validate', ({}, cbk) => {
|
|
66
|
+
// Exit early when there are no out peers
|
|
67
|
+
if (!args.out.length) {
|
|
68
|
+
return cbk();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return getChannels({lnd: args.lnd}, cbk);
|
|
72
|
+
}],
|
|
73
|
+
|
|
74
|
+
// Get the identity key to find the remote policies of channels
|
|
75
|
+
getIdentity: ['validate', ({}, cbk) => {
|
|
76
|
+
// Exit early when there are no filters that use fee variables
|
|
77
|
+
if (!(args.filters || []).some(isFeeFilter)) {
|
|
78
|
+
return cbk();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return getIdentity({lnd: args.lnd}, cbk);
|
|
82
|
+
}],
|
|
83
|
+
|
|
84
|
+
// Get the set of tags
|
|
85
|
+
getTags: ['validate', ({}, cbk) => {
|
|
86
|
+
// Exit early when there are no out peers or no way to read tags
|
|
87
|
+
if (!args.fs || !args.out.length) {
|
|
88
|
+
return cbk(null, {tags: []});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return getTags({fs: args.fs}, cbk);
|
|
92
|
+
}],
|
|
93
|
+
|
|
94
|
+
// Get the remote policies of channels for fee filter variables
|
|
95
|
+
getPolicies: ['getIdentity', ({getIdentity}, cbk) => {
|
|
96
|
+
// Exit early when there are no filters that use fee variables
|
|
97
|
+
if (!getIdentity) {
|
|
98
|
+
return cbk(null, []);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const id = getIdentity.public_key;
|
|
102
|
+
|
|
103
|
+
return getNode({lnd: args.lnd, public_key: id}, (err, res) => {
|
|
104
|
+
if (!!err) {
|
|
105
|
+
return cbk(err);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const policies = res.channels
|
|
109
|
+
.map(channel => channel.policies.find(n => n.public_key !== id))
|
|
110
|
+
.filter(n => !!n)
|
|
111
|
+
.map(policy => ({
|
|
112
|
+
base_fee_mtokens: policy.base_fee_mtokens,
|
|
113
|
+
fee_rate: policy.fee_rate,
|
|
114
|
+
public_key: policy.public_key,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
return cbk(null, policies);
|
|
118
|
+
});
|
|
119
|
+
}],
|
|
120
|
+
|
|
121
|
+
// Find the peers for each out query
|
|
122
|
+
getPeers: [
|
|
123
|
+
'getChannels',
|
|
124
|
+
'getPolicies',
|
|
125
|
+
'getTags',
|
|
126
|
+
({getChannels, getPolicies, getTags}, cbk) =>
|
|
127
|
+
{
|
|
128
|
+
// Exit early when there are no out peers
|
|
129
|
+
if (!args.out.length) {
|
|
130
|
+
return cbk(null, []);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const active = getChannels.channels.filter(n => !!n.is_active);
|
|
134
|
+
const filters = args.filters || [];
|
|
135
|
+
|
|
136
|
+
const peerKeys = uniq(active.map(n => n.partner_public_key));
|
|
137
|
+
|
|
138
|
+
// Check if a peer satisfies the filters
|
|
139
|
+
const isMatch = key => findPeerMatch({
|
|
140
|
+
filters,
|
|
141
|
+
channels: active,
|
|
142
|
+
nodes: [key],
|
|
143
|
+
policies: getPolicies,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return asyncMapSeries(args.out.map(n => String(n)), (query, cbk) => {
|
|
147
|
+
// Find tags that match on id or on alias
|
|
148
|
+
const tagMatches = getTags.tags.filter(tag => {
|
|
149
|
+
const alias = tag.alias || String();
|
|
150
|
+
|
|
151
|
+
const isAliasMatch = alias.toLowerCase() === query.toLowerCase();
|
|
152
|
+
const isIdMatch = tag.id.startsWith(query);
|
|
153
|
+
|
|
154
|
+
return isAliasMatch || isIdMatch;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Limit tag matches to tags with relevant peers
|
|
158
|
+
const matches = tagMatches.filter(tag => {
|
|
159
|
+
return (tag.nodes || []).some(n => peerKeys.includes(n));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const [tag, ...otherTags] = matches;
|
|
163
|
+
|
|
164
|
+
if (!!otherTags.length) {
|
|
165
|
+
return cbk([400, 'MultipleTagMatchesFoundForOutPeer', {matches}]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// A recognized tag must not fall through to an alias search
|
|
169
|
+
if (!tag && !!tagMatches.length) {
|
|
170
|
+
return cbk([400, 'NoActivePeersFoundForOutTag', {query}]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Exit early when the query is a tag, expanding to its peers
|
|
174
|
+
if (!!tag) {
|
|
175
|
+
const results = uniq(tag.nodes)
|
|
176
|
+
.filter(n => peerKeys.includes(n))
|
|
177
|
+
.map(key => isMatch(key));
|
|
178
|
+
|
|
179
|
+
const {failure} = results.find(n => !!n.failure) || {};
|
|
180
|
+
|
|
181
|
+
if (!!failure) {
|
|
182
|
+
return cbk([400, 'FailedToParseFilter', failure]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const keys = results.map(n => n.match).filter(n => !!n);
|
|
186
|
+
|
|
187
|
+
if (!keys.length) {
|
|
188
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return cbk(null, keys);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The query is not a tag so look for a public key or alias match
|
|
195
|
+
return findKey({
|
|
196
|
+
query,
|
|
197
|
+
channels: getChannels.channels,
|
|
198
|
+
lnd: args.lnd,
|
|
199
|
+
},
|
|
200
|
+
(err, res) => {
|
|
201
|
+
if (!!err) {
|
|
202
|
+
return cbk(err);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const key = res.public_key.toLowerCase();
|
|
206
|
+
|
|
207
|
+
// Exit early when there are no filters to apply
|
|
208
|
+
if (!filters.length) {
|
|
209
|
+
return cbk(null, [key]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const {failure, match} = isMatch(key);
|
|
213
|
+
|
|
214
|
+
if (!!failure) {
|
|
215
|
+
return cbk([400, 'FailedToParseFilter', failure]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!match) {
|
|
219
|
+
return cbk([400, 'NoPeerMatchesFoundToSatisfyOutboundFilter']);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return cbk(null, [match]);
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
cbk);
|
|
226
|
+
}],
|
|
227
|
+
|
|
228
|
+
// Final set of out peers
|
|
229
|
+
peers: ['getPeers', ({getPeers}, cbk) => {
|
|
230
|
+
return cbk(null, {public_keys: uniq(flatten(getPeers))});
|
|
231
|
+
}],
|
|
232
|
+
},
|
|
233
|
+
returnResult({reject, resolve, of: 'peers'}, cbk));
|
|
234
|
+
});
|
|
235
|
+
};
|
|
@@ -121,7 +121,7 @@ module.exports = (args, cbk) => {
|
|
|
121
121
|
in_through: args.in_through,
|
|
122
122
|
lnd: args.lnd,
|
|
123
123
|
logger: args.logger,
|
|
124
|
-
out_through: args.out_through,
|
|
124
|
+
out_through: [args.out_through].filter(n => !!n),
|
|
125
125
|
request: args.request,
|
|
126
126
|
timeout_minutes: args.timeout_minutes,
|
|
127
127
|
tokens: args.tokens,
|
package/network/pay.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncMap = require('async/map');
|
|
3
|
-
const {findKey} = require('ln-sync');
|
|
4
3
|
const {formatTokens} = require('ln-sync');
|
|
5
4
|
const {getChannels} = require('ln-service');
|
|
6
5
|
const {getIdentity} = require('ln-service');
|
|
@@ -14,6 +13,7 @@ const {describeRoute} = require('./../display');
|
|
|
14
13
|
const {describeRoutingFailure} = require('./../display');
|
|
15
14
|
const {getIgnores} = require('./../routing');
|
|
16
15
|
const {getTags} = require('./../tags');
|
|
16
|
+
const getOutPeers = require('./get_out_peers');
|
|
17
17
|
const probeDestination = require('./probe_destination');
|
|
18
18
|
|
|
19
19
|
const cltvDeltaBuffer = 3;
|
|
@@ -37,7 +37,8 @@ const withTotalFee = ({fee, ...res}) => ({total_fee: fee, ...res});
|
|
|
37
37
|
max_fee: <Max Fee Tokens Number>
|
|
38
38
|
max_paths: <Maximum Paths Number>
|
|
39
39
|
[message]: <Message String>
|
|
40
|
-
out: [<Out Through Peer With Public Key
|
|
40
|
+
out: [<Out Through Peer With Public Key, Alias, or Tag String>]
|
|
41
|
+
[out_filters]: [<Out Through Peer Filter Formula String>]
|
|
41
42
|
request: <BOLT 11 Payment Request String>
|
|
42
43
|
}
|
|
43
44
|
*/
|
|
@@ -113,8 +114,11 @@ module.exports = (args, cbk) => {
|
|
|
113
114
|
|
|
114
115
|
// Find public keys to pay out through
|
|
115
116
|
getOuts: ['validate', ({}, cbk) => {
|
|
116
|
-
return
|
|
117
|
-
|
|
117
|
+
return getOutPeers({
|
|
118
|
+
filters: args.out_filters,
|
|
119
|
+
fs: args.fs,
|
|
120
|
+
lnd: args.lnd,
|
|
121
|
+
out: args.out,
|
|
118
122
|
},
|
|
119
123
|
cbk);
|
|
120
124
|
}],
|
|
@@ -133,15 +137,16 @@ module.exports = (args, cbk) => {
|
|
|
133
137
|
getBaseIgnores: [
|
|
134
138
|
'getChannels',
|
|
135
139
|
'getIdentity',
|
|
140
|
+
'getOuts',
|
|
136
141
|
'getTags',
|
|
137
|
-
({getChannels, getIdentity, getTags}, cbk) =>
|
|
142
|
+
({getChannels, getIdentity, getOuts, getTags}, cbk) =>
|
|
138
143
|
{
|
|
139
144
|
// Exit early when there are no avoids
|
|
140
145
|
if (!args.avoid.length) {
|
|
141
146
|
return cbk(null, {ignore: []});
|
|
142
147
|
}
|
|
143
148
|
|
|
144
|
-
const [out] =
|
|
149
|
+
const [out, ...otherOuts] = getOuts.public_keys;
|
|
145
150
|
|
|
146
151
|
return getIgnores({
|
|
147
152
|
avoid: args.avoid,
|
|
@@ -149,7 +154,7 @@ module.exports = (args, cbk) => {
|
|
|
149
154
|
in_through: args.in_through,
|
|
150
155
|
lnd: args.lnd,
|
|
151
156
|
logger: args.logger,
|
|
152
|
-
out_through: out,
|
|
157
|
+
out_through: !otherOuts.length ? out : undefined,
|
|
153
158
|
public_key: getIdentity.public_key,
|
|
154
159
|
tags: getTags.tags,
|
|
155
160
|
},
|
|
@@ -168,14 +173,14 @@ module.exports = (args, cbk) => {
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
// Exit early when there is no outbound restriction
|
|
171
|
-
if (!getOuts.length) {
|
|
176
|
+
if (!getOuts.public_keys.length) {
|
|
172
177
|
return cbk(null, {ignore: getBaseIgnores.ignore});
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
return getSyntheticOutIgnores({
|
|
176
181
|
ignore: getBaseIgnores.ignore,
|
|
177
182
|
lnd: args.lnd,
|
|
178
|
-
out: getOuts.
|
|
183
|
+
out: getOuts.public_keys,
|
|
179
184
|
},
|
|
180
185
|
cbk);
|
|
181
186
|
}],
|
|
@@ -183,19 +188,14 @@ module.exports = (args, cbk) => {
|
|
|
183
188
|
// Make single-path payment
|
|
184
189
|
singlePathPay: [
|
|
185
190
|
'getBaseIgnores',
|
|
186
|
-
'getOuts',
|
|
191
|
+
'getOuts',
|
|
192
|
+
({getBaseIgnores, getOuts}, cbk) =>
|
|
187
193
|
{
|
|
188
194
|
// Exit early when doing a multi-path
|
|
189
195
|
if (args.max_paths !== singlePath) {
|
|
190
196
|
return cbk();
|
|
191
197
|
}
|
|
192
198
|
|
|
193
|
-
const [outThrough, otherKey] = getOuts.map(n => n.public_key);
|
|
194
|
-
|
|
195
|
-
if (!!otherKey) {
|
|
196
|
-
return cbk([501, 'CannotPayThroughMultipleOutPeersOnSinglePath']);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
199
|
return probeDestination({
|
|
200
200
|
fs: args.fs,
|
|
201
201
|
ignore: getBaseIgnores.ignore,
|
|
@@ -206,7 +206,7 @@ module.exports = (args, cbk) => {
|
|
|
206
206
|
lnd: args.lnd,
|
|
207
207
|
max_fee: args.max_fee,
|
|
208
208
|
message: args.message,
|
|
209
|
-
out_through:
|
|
209
|
+
out_through: getOuts.public_keys,
|
|
210
210
|
request: args.request,
|
|
211
211
|
},
|
|
212
212
|
cbk);
|
package/network/probe.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const asyncMap = require('async/map');
|
|
3
2
|
const asyncWhilst = require('async/whilst');
|
|
4
|
-
const {findKey} = require('ln-sync');
|
|
5
3
|
const {formatTokens} = require('ln-sync');
|
|
6
4
|
const {getChannel} = require('ln-service');
|
|
7
5
|
const {getChannels} = require('ln-service');
|
|
@@ -19,6 +17,7 @@ const {describeRoutingFailure} = require('./../display');
|
|
|
19
17
|
const {getIcons} = require('./../display');
|
|
20
18
|
const {getIgnores} = require('./../routing');
|
|
21
19
|
const {getTags} = require('./../tags');
|
|
20
|
+
const getOutPeers = require('./get_out_peers');
|
|
22
21
|
const probeDestination = require('./probe_destination');
|
|
23
22
|
|
|
24
23
|
const defaultFinalCltvDelta = 144;
|
|
@@ -47,7 +46,8 @@ const withTotalFee = ({fee, ...res}) => ({total_fee: fee, ...res});
|
|
|
47
46
|
logger: <Winston Logger Object>
|
|
48
47
|
max_fee: <Max Fee Tokens Number>
|
|
49
48
|
[max_paths]: <Maximum Probe Paths Number>
|
|
50
|
-
out: [<Out Through Peer With Public Key
|
|
49
|
+
out: [<Out Through Peer With Public Key, Alias, or Tag String>]
|
|
50
|
+
[out_filters]: [<Out Through Peer Filter Formula String>]
|
|
51
51
|
[request]: <BOLT 11 Encoded Payment Request String>
|
|
52
52
|
[timeout_minutes]: <Stop Searching For Routes After N Minutes Number>
|
|
53
53
|
[tokens]: <Tokens Amount String>
|
|
@@ -148,8 +148,11 @@ module.exports = (args, cbk) => {
|
|
|
148
148
|
|
|
149
149
|
// Find public keys to pay out through
|
|
150
150
|
getOuts: ['validate', ({}, cbk) => {
|
|
151
|
-
return
|
|
152
|
-
|
|
151
|
+
return getOutPeers({
|
|
152
|
+
filters: args.out_filters,
|
|
153
|
+
fs: args.fs,
|
|
154
|
+
lnd: args.lnd,
|
|
155
|
+
out: args.out,
|
|
153
156
|
},
|
|
154
157
|
cbk);
|
|
155
158
|
}],
|
|
@@ -197,15 +200,16 @@ module.exports = (args, cbk) => {
|
|
|
197
200
|
getBaseIgnores: [
|
|
198
201
|
'getChannels',
|
|
199
202
|
'getIdentity',
|
|
203
|
+
'getOuts',
|
|
200
204
|
'getTags',
|
|
201
|
-
({getChannels, getIdentity, getTags}, cbk) =>
|
|
205
|
+
({getChannels, getIdentity, getOuts, getTags}, cbk) =>
|
|
202
206
|
{
|
|
203
207
|
// Exit early when there are no avoids
|
|
204
208
|
if (!args.avoid.length) {
|
|
205
209
|
return cbk(null, {ignore: []});
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
const [out] =
|
|
212
|
+
const [out, ...otherOuts] = getOuts.public_keys;
|
|
209
213
|
|
|
210
214
|
return getIgnores({
|
|
211
215
|
avoid: args.avoid,
|
|
@@ -213,7 +217,7 @@ module.exports = (args, cbk) => {
|
|
|
213
217
|
in_through: args.in_through,
|
|
214
218
|
lnd: args.lnd,
|
|
215
219
|
logger: args.logger,
|
|
216
|
-
out_through: out,
|
|
220
|
+
out_through: !otherOuts.length ? out : undefined,
|
|
217
221
|
public_key: getIdentity.public_key,
|
|
218
222
|
tags: getTags.tags,
|
|
219
223
|
},
|
|
@@ -232,14 +236,14 @@ module.exports = (args, cbk) => {
|
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
// Exit early when there is no outbound restriction
|
|
235
|
-
if (!getOuts.length) {
|
|
239
|
+
if (!getOuts.public_keys.length) {
|
|
236
240
|
return cbk(null, {ignore: getBaseIgnores.ignore});
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
return getSyntheticOutIgnores({
|
|
240
244
|
ignore: getBaseIgnores.ignore,
|
|
241
245
|
lnd: args.lnd,
|
|
242
|
-
out: getOuts.
|
|
246
|
+
out: getOuts.public_keys,
|
|
243
247
|
},
|
|
244
248
|
cbk);
|
|
245
249
|
}],
|
|
@@ -256,13 +260,6 @@ module.exports = (args, cbk) => {
|
|
|
256
260
|
return cbk();
|
|
257
261
|
}
|
|
258
262
|
|
|
259
|
-
// Exit early when probing on a single path
|
|
260
|
-
if (getOuts.length > singlePath) {
|
|
261
|
-
return cbk([501, 'MultipleOutPeersNotSupportedWithSinglePath']);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const [outThrough] = getOuts.map(n => n.public_key);
|
|
265
|
-
|
|
266
263
|
return probeDestination({
|
|
267
264
|
tokens,
|
|
268
265
|
destination: args.destination,
|
|
@@ -273,7 +270,7 @@ module.exports = (args, cbk) => {
|
|
|
273
270
|
lnd: args.lnd,
|
|
274
271
|
logger: args.logger,
|
|
275
272
|
max_fee: args.max_fee,
|
|
276
|
-
out_through:
|
|
273
|
+
out_through: getOuts.public_keys,
|
|
277
274
|
request: args.request,
|
|
278
275
|
},
|
|
279
276
|
cbk);
|
|
@@ -36,6 +36,7 @@ const feeBuffer = fees => fees * 2;
|
|
|
36
36
|
const {floor} = Math;
|
|
37
37
|
const fromKeyType = '34349339';
|
|
38
38
|
const htlcOutputSize = 43;
|
|
39
|
+
const {isArray} = Array;
|
|
39
40
|
const isBlindedHop = hop => !!hop.encrypted_data && !hop.path_key;
|
|
40
41
|
const keySendPreimageType = '5482373484';
|
|
41
42
|
const makeNonce = () => randomBytes(32).toString('hex');
|
|
@@ -49,6 +50,7 @@ const {now} = Date;
|
|
|
49
50
|
const pathChannel = hop => isBlindedHop(hop) ? blinded : hop.channel;
|
|
50
51
|
const rate = n => n.commit_transaction_fee / (n.commit_transaction_weight / 4);
|
|
51
52
|
const signatureType = '34349337';
|
|
53
|
+
const singlePeer = 1;
|
|
52
54
|
const tokAsMtok = tokens => (BigInt(tokens || 0) * BigInt(1e3)).toString();
|
|
53
55
|
|
|
54
56
|
/** Determine if a destination can be paid by probing it
|
|
@@ -78,7 +80,7 @@ const tokAsMtok = tokens => (BigInt(tokens || 0) * BigInt(1e3)).toString();
|
|
|
78
80
|
type: <Additional Message To Final Destination Type Number String>
|
|
79
81
|
value: <Message To Final Destination Raw Value Hex Encoded String>
|
|
80
82
|
}]
|
|
81
|
-
[out_through]: <Out Through Peer With Public Key Hex String>
|
|
83
|
+
[out_through]: [<Out Through Any Peer With Public Key Hex String>]
|
|
82
84
|
[request]: <Payment Request String>
|
|
83
85
|
[timeout_minutes]: <Stop Searching For Route After N Minutes Number>
|
|
84
86
|
[tokens]: <Tokens Number>
|
|
@@ -109,13 +111,24 @@ module.exports = (args, cbk) => {
|
|
|
109
111
|
return cbk([400, "ExpectedLoggerToProbeDestination"]);
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
if (!!args.out_through && !isArray(args.out_through)) {
|
|
115
|
+
return cbk([400, 'ExpectedArrayOfOutThroughPeersToProbe']);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const outPeers = args.out_through || [];
|
|
119
|
+
|
|
120
|
+
// Finding the maximum is limited by a single outgoing channel
|
|
121
|
+
if (!!args.find_max && outPeers.length > singlePeer) {
|
|
122
|
+
return cbk([501, 'FindMaxNotSupportedWithMultipleOutThroughPeers']);
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
return cbk();
|
|
113
126
|
},
|
|
114
127
|
|
|
115
128
|
// Get channels to determine an outgoing channel id restriction
|
|
116
129
|
getChannels: ['validate', ({}, cbk) => {
|
|
117
130
|
// Exit early when there is no need to add an outgoing channel id
|
|
118
|
-
if (!args.out_through) {
|
|
131
|
+
if (!args.out_through || !args.out_through.length) {
|
|
119
132
|
return cbk();
|
|
120
133
|
}
|
|
121
134
|
|
|
@@ -395,12 +408,12 @@ module.exports = (args, cbk) => {
|
|
|
395
408
|
}
|
|
396
409
|
|
|
397
410
|
const {channels} = getChannels;
|
|
398
|
-
const
|
|
411
|
+
const outPeers = args.out_through;
|
|
399
412
|
const tokens = args.tokens || to.tokens || defaultTokens;
|
|
400
413
|
|
|
401
414
|
const withPeer = channels
|
|
402
415
|
.filter(n => !!n.is_active)
|
|
403
|
-
.filter(n => n.partner_public_key
|
|
416
|
+
.filter(n => outPeers.includes(n.partner_public_key));
|
|
404
417
|
|
|
405
418
|
if (!withPeer.length) {
|
|
406
419
|
return cbk([404, 'NoActiveChannelWithOutgoingPeer']);
|
|
@@ -417,6 +430,12 @@ module.exports = (args, cbk) => {
|
|
|
417
430
|
return cbk([404, 'NoOutboundPeerWithSufficientBalance']);
|
|
418
431
|
}
|
|
419
432
|
|
|
433
|
+
// Exit early when going out through any channel of multiple peers
|
|
434
|
+
if (outPeers.length > singlePeer) {
|
|
435
|
+
return cbk(null, {ids: withBalance.map(n => n.id)});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// A single peer goes out through one channel with enough balance
|
|
420
439
|
const attribute = 'local_balance';
|
|
421
440
|
|
|
422
441
|
const [channel] = sortBy({attribute, array: withBalance}).sorted;
|
|
@@ -427,9 +446,9 @@ module.exports = (args, cbk) => {
|
|
|
427
446
|
const fees = channel.commit_transaction_fee + bufferFees;
|
|
428
447
|
const spendable = channel.local_balance - reserve;
|
|
429
448
|
|
|
430
|
-
const maxPayable = floor(spendable - feeBuffer(fees));
|
|
449
|
+
const maxPayable = floor(spendable - feeBuffer(fees)) || args.find_max;
|
|
431
450
|
|
|
432
|
-
return cbk(null, {
|
|
451
|
+
return cbk(null, {ids: [channel.id], max: maxPayable});
|
|
433
452
|
}],
|
|
434
453
|
|
|
435
454
|
// Log sending towards destination
|
|
@@ -502,7 +521,7 @@ module.exports = (args, cbk) => {
|
|
|
502
521
|
max_fee: args.max_fee,
|
|
503
522
|
max_fee_mtokens: args.max_fee_mtokens,
|
|
504
523
|
mtokens: !BigInt(to.mtokens) ? tokAsMtok(defaultTokens) : to.mtokens,
|
|
505
|
-
|
|
524
|
+
outgoing_channels: !!outId ? outId.ids : undefined,
|
|
506
525
|
paths: to.paths || undefined,
|
|
507
526
|
payment: to.payment,
|
|
508
527
|
routes: to.routes,
|
package/network/push_payment.js
CHANGED
|
@@ -465,7 +465,7 @@ module.exports = (args, cbk) => {
|
|
|
465
465
|
type: (quizStart + i).toString(),
|
|
466
466
|
value: utf8AsHex(answer),
|
|
467
467
|
})),
|
|
468
|
-
out_through: getOutKey,
|
|
468
|
+
out_through: [getOutKey].filter(n => !!n),
|
|
469
469
|
request: send.request,
|
|
470
470
|
timeout_minutes: args.timeout_minutes,
|
|
471
471
|
tokens: send.tokens,
|
package/network/remove_peer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncMapSeries = require('async/mapSeries');
|
|
3
3
|
const {closeChannel} = require('ln-service');
|
|
4
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
4
5
|
const {decodeChanId} = require('bolt07');
|
|
5
6
|
const {getChainFeeRate} = require('ln-service');
|
|
6
7
|
const {getChainTransactions} = require('ln-service');
|
|
@@ -9,7 +10,7 @@ const {getHeight} = require('ln-service');
|
|
|
9
10
|
const {getNetwork} = require('ln-sync');
|
|
10
11
|
const {getPendingChannels} = require('ln-service');
|
|
11
12
|
const {returnResult} = require('asyncjs-util');
|
|
12
|
-
const {
|
|
13
|
+
const {sizeOfTransaction} = require('@alexbosworth/blockchain');
|
|
13
14
|
|
|
14
15
|
const {getMempoolSize} = require('./../chain');
|
|
15
16
|
const getPeers = require('./get_peers');
|
|
@@ -21,7 +22,6 @@ const defaultDays = 365 * 2;
|
|
|
21
22
|
const estimateDisk = n => Math.round(n * 500 / 1e6 * 10) / 10;
|
|
22
23
|
const fastConf = 6;
|
|
23
24
|
const {floor} = Math;
|
|
24
|
-
const {fromHex} = Transaction;
|
|
25
25
|
const getMempoolRetries = 10;
|
|
26
26
|
const iconDisabled = channel => !channel.is_active ? '💀 ' : '';
|
|
27
27
|
const iconPending = channel => channel.pending_payments.length ? '💸 ' : ''
|
|
@@ -528,15 +528,18 @@ module.exports = (args, cbk) => {
|
|
|
528
528
|
return;
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
const
|
|
531
|
+
const {transaction} = matching;
|
|
532
532
|
|
|
533
|
-
const
|
|
533
|
+
const {outputs} = componentsOfTransaction({transaction});
|
|
534
|
+
const {vsize} = sizeOfTransaction({transaction});
|
|
535
|
+
|
|
536
|
+
const fee = channel.capacity - sumOf(outputs.map(n => n.tokens));
|
|
534
537
|
|
|
535
538
|
return {
|
|
536
539
|
close_transaction_id: matching.id,
|
|
537
540
|
peer_pays_closing_fee: channel.is_partner_initiated === true,
|
|
538
541
|
transaction_fee: tokensAsBigUnit(fee),
|
|
539
|
-
transaction_fee_rate: asRate(fee /
|
|
542
|
+
transaction_fee_rate: asRate(fee / vsize),
|
|
540
543
|
};
|
|
541
544
|
});
|
|
542
545
|
|
|
@@ -271,7 +271,7 @@ module.exports = (args, cbk) => {
|
|
|
271
271
|
lnd: args.lnd,
|
|
272
272
|
logger: args.logger,
|
|
273
273
|
in_through: getInKey.public_key,
|
|
274
|
-
out_through: getOutKey.public_key,
|
|
274
|
+
out_through: [getOutKey.public_key].filter(n => !!n),
|
|
275
275
|
timeout_minutes: args.timeout_minutes,
|
|
276
276
|
tokens: parseAmount.tokens,
|
|
277
277
|
},
|
|
@@ -335,7 +335,7 @@ module.exports = (args, cbk) => {
|
|
|
335
335
|
in_through: getInKey.public_key,
|
|
336
336
|
is_real_payment: true,
|
|
337
337
|
max_fee: maxFee,
|
|
338
|
-
out_through: getOutKey.public_key,
|
|
338
|
+
out_through: [getOutKey.public_key].filter(n => !!n),
|
|
339
339
|
request: createInvoice.request,
|
|
340
340
|
timeout_minutes: args.timeout_minutes,
|
|
341
341
|
},
|
package/package.json
CHANGED
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
"@alexbosworth/fiat": "2.0.0",
|
|
21
21
|
"@alexbosworth/formulas": "1.5.0",
|
|
22
22
|
"@alexbosworth/html2unicode": "1.1.5",
|
|
23
|
-
"@alexbosworth/node-fetch": "2.6.2",
|
|
24
|
-
"abort-controller": "3.0.0",
|
|
25
23
|
"asciichart": "1.5.25",
|
|
26
24
|
"async": "3.2.6",
|
|
27
25
|
"asyncjs-util": "1.2.12",
|
|
@@ -40,7 +38,7 @@
|
|
|
40
38
|
"ini": "7.0.0",
|
|
41
39
|
"inquirer": "14.2.2",
|
|
42
40
|
"ln-accounting": "10.0.7",
|
|
43
|
-
"ln-service": "60.
|
|
41
|
+
"ln-service": "60.1.1",
|
|
44
42
|
"ln-sync": "8.0.6",
|
|
45
43
|
"ln-telegram": "8.0.6",
|
|
46
44
|
"moment": "2.31.0",
|
|
@@ -107,5 +105,5 @@
|
|
|
107
105
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
108
106
|
"test": "node --experimental-test-coverage --test 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/offchain/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
|
109
107
|
},
|
|
110
|
-
"version": "24.
|
|
108
|
+
"version": "24.2.0"
|
|
111
109
|
}
|
|
@@ -123,6 +123,8 @@ module.exports = (args, cbk) => {
|
|
|
123
123
|
|
|
124
124
|
// Get single path
|
|
125
125
|
getSinglePath: ['validate', ({}, cbk) => {
|
|
126
|
+
const channel = args.outgoing_channel;
|
|
127
|
+
|
|
126
128
|
return executeProbe({
|
|
127
129
|
cltv_delta: args.cltv_delta,
|
|
128
130
|
destination: args.destination,
|
|
@@ -131,7 +133,7 @@ module.exports = (args, cbk) => {
|
|
|
131
133
|
lnd: args.lnd,
|
|
132
134
|
logger: args.logger,
|
|
133
135
|
max_fee: args.max_fee,
|
|
134
|
-
|
|
136
|
+
outgoing_channels: !!channel ? [channel] : undefined,
|
|
135
137
|
payment: args.payment,
|
|
136
138
|
routes: args.routes,
|
|
137
139
|
tokens: args.tokens,
|
package/swaps/rebalance.js
CHANGED
|
@@ -593,7 +593,7 @@ module.exports = (args, cbk) => {
|
|
|
593
593
|
lnd: args.lnd,
|
|
594
594
|
log_failure: args.log_failure,
|
|
595
595
|
max_fee_mtokens: maxFeeMtokens,
|
|
596
|
-
out_through: getOutbound.public_key,
|
|
596
|
+
out_through: [getOutbound.public_key],
|
|
597
597
|
timeout_minutes: args.timeout_minutes,
|
|
598
598
|
},
|
|
599
599
|
cbk);
|
package/swaps/swap_api_key.js
CHANGED
|
@@ -10,7 +10,7 @@ const getPaidService = require('./get_paid_service');
|
|
|
10
10
|
|
|
11
11
|
{
|
|
12
12
|
[api_key]: <Swap CBOR Encoded API Key Hex String>
|
|
13
|
-
[fetch]: <
|
|
13
|
+
[fetch]: <Fetch Function>
|
|
14
14
|
[is_purchase]: <Purchase a New API Key Bool>
|
|
15
15
|
[macaroon]: <Macaroon Hex Encoded String>
|
|
16
16
|
[lnd]: <Authenticated LND API gRPC API Object>
|
package/swaps/swap_out.js
CHANGED
|
@@ -729,7 +729,7 @@ module.exports = (args, cbk) => {
|
|
|
729
729
|
logger: args.logger,
|
|
730
730
|
max_fee: maxExecutionFeeTokens,
|
|
731
731
|
mtokens: decodeExecutionRequest.mtokens,
|
|
732
|
-
|
|
732
|
+
outgoing_channels: !!channel ? [channel.id] : undefined,
|
|
733
733
|
payment: decodeExecutionRequest.payment,
|
|
734
734
|
routes: decodeExecutionRequest.routes,
|
|
735
735
|
tagged: !!getIcons ? getIcons.nodes : undefined,
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
const {Transaction} = require('bitcoinjs-lib');
|
|
2
|
-
|
|
3
|
-
const {fromHex} = Transaction;
|
|
4
|
-
const idFromHash = hash => hash.reverse().toString('hex');
|
|
5
|
-
|
|
6
|
-
/** Transaction records from parents of an original transaction
|
|
7
|
-
|
|
8
|
-
{
|
|
9
|
-
ended: [{
|
|
10
|
-
capacity: <Closed Channel Capacity Tokens Number>
|
|
11
|
-
[close_transaction_id]: <Closing Transaction Id Hex String>
|
|
12
|
-
final_local_balance: <Channel Close Final Local Balance Tokens Number>
|
|
13
|
-
final_time_locked_balance: <Closed Channel Timelocked Tokens Number>
|
|
14
|
-
[id]: <Closed Standard Format Channel Id String>
|
|
15
|
-
is_cooperative_close: <Is Cooperative Close Bool>
|
|
16
|
-
is_local_force_close: <Is Local Force Close Bool>
|
|
17
|
-
is_remote_force_close: <Is Remote Force Close Bool>
|
|
18
|
-
partner_public_key: <Partner Public Key Hex String>
|
|
19
|
-
transaction_id: <Channel Funding Transaction Id Hex String>
|
|
20
|
-
transaction_vout: <Channel Funding Output Index Number>
|
|
21
|
-
}]
|
|
22
|
-
id: <Transaction Id Hex String>
|
|
23
|
-
original: <Original Transaction Id Hex String>
|
|
24
|
-
pending: [{
|
|
25
|
-
[close_transaction_id]: <Channel Closing Transaction Id String>
|
|
26
|
-
is_closing: <Channel Is Closing Bool>
|
|
27
|
-
[is_partner_initiated]: <Channel Partner Initiated Channel Bool>
|
|
28
|
-
local_balance: <Channel Local Tokens Balance Number>
|
|
29
|
-
partner_public_key: <Channel Peer Public Key String>
|
|
30
|
-
[pending_balance]: <Tokens Pending Recovery Number>
|
|
31
|
-
[recovered_tokens]: <Tokens Recovered From Close Number>
|
|
32
|
-
[timelock_expiration]: <Pending Tokens Block Height Timelock Number>
|
|
33
|
-
[transaction_fee]: <Funding Transaction Fee Tokens Number>
|
|
34
|
-
transaction_id: <Channel Funding Transaction Id String>
|
|
35
|
-
transaction_vout: <Channel Funding Transaction Vout Number>
|
|
36
|
-
[transaction_weight]: <Funding Transaction Weight Number>
|
|
37
|
-
}]
|
|
38
|
-
txs: [{
|
|
39
|
-
[block_id]: <Block Hash String>
|
|
40
|
-
[confirmation_count]: <Confirmation Count Number>
|
|
41
|
-
[confirmation_height]: <Confirmation Block Height Number>
|
|
42
|
-
created_at: <Created ISO 8601 Date String>
|
|
43
|
-
[fee]: <Fees Paid Tokens Number>
|
|
44
|
-
id: <Transaction Id String>
|
|
45
|
-
is_confirmed: <Is Confirmed Bool>
|
|
46
|
-
is_outgoing: <Transaction Outbound Bool>
|
|
47
|
-
output_addresses: [<Address String>]
|
|
48
|
-
tokens: <Tokens Including Fee Number>
|
|
49
|
-
[transaction]: <Raw Transaction Hex String>
|
|
50
|
-
}]
|
|
51
|
-
vout: <Spent Transaction Output Index>
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@returns
|
|
55
|
-
{
|
|
56
|
-
records: [{
|
|
57
|
-
action: <Channel Action String>
|
|
58
|
-
[balance]: <Channel Balance Tokens Number>
|
|
59
|
-
[capacity]: <Channel Capacity Value Number>
|
|
60
|
-
[channel]: <Channel Standard Format Id String>
|
|
61
|
-
[close_tx]: <Channel Closing Transaction Id Hex String>
|
|
62
|
-
[open_tx]: <Channel Opening Transaction Id Hex String>
|
|
63
|
-
[timelock]: <Channel Funds Timelocked Until Height Number>
|
|
64
|
-
with: <Channel Peer Public Key Hex String>
|
|
65
|
-
}]
|
|
66
|
-
}
|
|
67
|
-
*/
|
|
68
|
-
module.exports = ({ended, id, original, pending, txs, vout}) => {
|
|
69
|
-
const records = [];
|
|
70
|
-
const spendClosing = pending.find(n => n.close_transaction_id === id);
|
|
71
|
-
const spendTx = txs.find(tx => tx.id === id);
|
|
72
|
-
|
|
73
|
-
const spendPending = pending.filter(chan => {
|
|
74
|
-
return chan.transaction_id === id && chan.transaction_vout === vout;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (!!spendPending.length) {
|
|
78
|
-
spendPending
|
|
79
|
-
.filter(pending => !!pending.is_closing)
|
|
80
|
-
.forEach(pending => {
|
|
81
|
-
return records.push({
|
|
82
|
-
action: 'channel_closing',
|
|
83
|
-
balance: pending.pending_balance,
|
|
84
|
-
timelock: pending.timelock_expiration,
|
|
85
|
-
with: pending.partner_public_key,
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!!spendClosing && spendClosing.is_partner_initiated) {
|
|
91
|
-
records.push({
|
|
92
|
-
action: 'peer_force_closing_channel',
|
|
93
|
-
balance: spendClosing.pending_balance,
|
|
94
|
-
timelock: spendClosing.timelock_expiration,
|
|
95
|
-
with: spendClosing.partner_public_key,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!!spendTx) {
|
|
100
|
-
fromHex(spendTx.transaction).ins.forEach(input => {
|
|
101
|
-
const grandParentTx = txs.find(n => n.id === original);
|
|
102
|
-
|
|
103
|
-
if (!grandParentTx) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return fromHex(grandParentTx.transaction).ins.forEach(grandIn => {
|
|
108
|
-
const grandTx = txs.find(n => n.id === idFromHash(grandIn.hash));
|
|
109
|
-
|
|
110
|
-
if (!grandTx) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return fromHex(grandTx.transaction).ins.forEach(greatIn => {
|
|
115
|
-
const greatTx = txs.find(n => n.id === idFromHash(greatIn.hash));
|
|
116
|
-
|
|
117
|
-
const closingTime = ended.find(chan => {
|
|
118
|
-
return chan.close_transaction_id === idFromHash(greatIn.hash);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (!!closingTime && closingTime.is_local_force_close) {
|
|
122
|
-
records.push({
|
|
123
|
-
action: 'force_closed_channel',
|
|
124
|
-
balance: closingTime.final_time_locked_balance,
|
|
125
|
-
capacity: closingTime.capacity,
|
|
126
|
-
channel: closingTime.id,
|
|
127
|
-
close_tx: closingTime.close_transaction_id,
|
|
128
|
-
open_tx: closingTime.transaction_id,
|
|
129
|
-
with: closingTime.partner_public_key,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const spendClose = ended.find(n => n.close_transaction_id === id);
|
|
138
|
-
|
|
139
|
-
// Exit early when there is no spend of a closed channel
|
|
140
|
-
if (!spendClose) {
|
|
141
|
-
return {records};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!!spendClose.is_cooperative_close) {
|
|
145
|
-
records.push({
|
|
146
|
-
action: 'cooperatively_closed_channel',
|
|
147
|
-
balance: spendClose.final_local_balance,
|
|
148
|
-
capacity: spendClose.capacity,
|
|
149
|
-
channel: spendClose.id,
|
|
150
|
-
close_tx: spendClose.close_transaction_id,
|
|
151
|
-
open_tx: spendClose.transaction_id,
|
|
152
|
-
with: spendClose.partner_public_key,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!!spendClose.is_local_force_close) {
|
|
157
|
-
const balance = spendClose.final_time_locked_balance;
|
|
158
|
-
|
|
159
|
-
records.push({
|
|
160
|
-
action: 'force_closed_channel',
|
|
161
|
-
balance: balance || spendClose.final_local_balance,
|
|
162
|
-
capacity: spendClose.capacity,
|
|
163
|
-
channel: spendClose.id,
|
|
164
|
-
close_tx: spendClose.close_transaction_id,
|
|
165
|
-
open_tx: spendClose.transaction_id,
|
|
166
|
-
with: spendClose.partner_public_key,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (!!spendClose.is_remote_force_close) {
|
|
171
|
-
records.push({
|
|
172
|
-
action: 'peer_force_closed_channel',
|
|
173
|
-
balance: spendClose.final_local_balance,
|
|
174
|
-
capacity: spendClose.capacity,
|
|
175
|
-
channel: spendClose.id,
|
|
176
|
-
close_tx: spendClose.close_transaction_id,
|
|
177
|
-
open_tx: spendClose.transaction_id,
|
|
178
|
-
with: spendClose.partner_public_key,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {records};
|
|
183
|
-
};
|