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 +8 -0
- package/README.md +2 -2
- package/bos +20 -21
- package/commands/interrogate.js +15 -1
- package/network/get_node_pending.js +329 -0
- package/network/get_pending.js +81 -0
- package/network/get_pending_paths.js +96 -0
- package/network/index.js +2 -0
- package/network/pending_payments.js +105 -0
- package/network/pending_summary.js +204 -0
- package/network/probe_destination.js +19 -14
- package/package.json +3 -4
- package/services/accept_balanced_channel.js +45 -44
- package/services/initiate_balanced_channel.js +70 -53
- package/services/recover_transit_funds.js +27 -20
- package/telegram/get_telegram_bot.js +10 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const flatten = arr => [].concat(...arr);
|
|
2
|
+
|
|
3
|
+
/** Derive pending forwarding and sending HTLCs from channels
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
channels: [{
|
|
7
|
+
id: <Channel Id String>
|
|
8
|
+
partner_public_key: <Peer Public Key Hex String>
|
|
9
|
+
pending_payments: [{
|
|
10
|
+
id: <Payment Preimage Hash Hex String>
|
|
11
|
+
[in_channel]: <Forward Inbound From Channel Id String>
|
|
12
|
+
[in_payment]: <Payment Index on Inbound Channel Number>
|
|
13
|
+
[is_forward]: <Payment is a Forward Bool>
|
|
14
|
+
is_outgoing: <Payment Is Outgoing Bool>
|
|
15
|
+
[out_channel]: <Forward Outbound To Channel Id String>
|
|
16
|
+
[out_payment]: <Payment Index on Outbound Channel Number>
|
|
17
|
+
[payment]: <Payment Attempt Id Number>
|
|
18
|
+
timeout: <Chain Height Expiration Number>
|
|
19
|
+
tokens: <Payment Tokens Number>
|
|
20
|
+
}]
|
|
21
|
+
}]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@returns
|
|
25
|
+
{
|
|
26
|
+
forwarding: [{
|
|
27
|
+
fee: <Routing Fee Tokens Number>
|
|
28
|
+
in_peer: <Forwarding From Peer Public Key Hex String>
|
|
29
|
+
out_peer: <Forwarding To Peer Public Key Hex String>
|
|
30
|
+
payment: <Forwarding Payment Hash Hex String>
|
|
31
|
+
timeout: <Chain Height Expiration Number>
|
|
32
|
+
tokens: <Forwarding Tokens Amount Number>
|
|
33
|
+
}]
|
|
34
|
+
sending: [{
|
|
35
|
+
id: <Sending Payment Hash Hex String>
|
|
36
|
+
out_channel: <Sending Out Channel Id String>
|
|
37
|
+
out_peer: <Sending Out Peer Public Key Hex String>
|
|
38
|
+
timeout: <Sending Timeout Block Height Number>
|
|
39
|
+
tokens: <Sending Tokens Amount Number>
|
|
40
|
+
}]
|
|
41
|
+
}
|
|
42
|
+
*/
|
|
43
|
+
module.exports = ({channels}) => {
|
|
44
|
+
// Outgoing HTLCs that are not forwards are payments or probes being sent
|
|
45
|
+
const sending = flatten(channels.map(channel => {
|
|
46
|
+
return channel.pending_payments
|
|
47
|
+
.filter(n => !n.is_forward && !!n.is_outgoing)
|
|
48
|
+
.map(payment => ({
|
|
49
|
+
id: payment.id,
|
|
50
|
+
out_channel: channel.id,
|
|
51
|
+
out_peer: channel.partner_public_key,
|
|
52
|
+
timeout: payment.timeout,
|
|
53
|
+
tokens: payment.tokens,
|
|
54
|
+
}));
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
// Outgoing forward HTLCs reference the inbound HTLC that they are relaying
|
|
58
|
+
const outbound = flatten(channels.map(channel => {
|
|
59
|
+
return channel.pending_payments
|
|
60
|
+
.filter(n => !!n.is_forward && !!n.is_outgoing && !!n.in_channel)
|
|
61
|
+
.map(payment => ({
|
|
62
|
+
id: payment.id,
|
|
63
|
+
in_channel: payment.in_channel,
|
|
64
|
+
in_payment: payment.in_payment,
|
|
65
|
+
out_channel: channel.id,
|
|
66
|
+
out_peer: channel.partner_public_key,
|
|
67
|
+
tokens: payment.tokens,
|
|
68
|
+
}));
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// Pair up outgoing forward HTLCs with the inbound HTLCs they are relaying
|
|
72
|
+
const forwarding = outbound.map(htlc => {
|
|
73
|
+
const inboundChannel = channels.find(n => n.id === htlc.in_channel);
|
|
74
|
+
|
|
75
|
+
// Exit early when the inbound channel is not a known channel
|
|
76
|
+
if (!inboundChannel) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The inbound HTLC is the one forwarding to the outbound channel
|
|
81
|
+
const inbound = inboundChannel.pending_payments.find(payment => {
|
|
82
|
+
const isForwardIn = !payment.is_outgoing && !!payment.is_forward;
|
|
83
|
+
const isSameIndex = payment.payment === htlc.in_payment;
|
|
84
|
+
const isToOutChannel = payment.out_channel === htlc.out_channel;
|
|
85
|
+
|
|
86
|
+
return isForwardIn && isToOutChannel && isSameIndex;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Exit early when the inbound HTLC is not present on the inbound channel
|
|
90
|
+
if (!inbound) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
fee: inbound.tokens - htlc.tokens,
|
|
96
|
+
in_peer: inboundChannel.partner_public_key,
|
|
97
|
+
out_peer: htlc.out_peer,
|
|
98
|
+
payment: htlc.id,
|
|
99
|
+
timeout: inbound.timeout,
|
|
100
|
+
tokens: htlc.tokens,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {sending, forwarding: forwarding.filter(n => !!n)};
|
|
105
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const {bold} = require('colorette');
|
|
2
|
+
const {formatTokens} = require('ln-sync');
|
|
3
|
+
|
|
4
|
+
const {formatDuration} = require('./../display');
|
|
5
|
+
|
|
6
|
+
const blocksAsMs = blocks => blocks * 1000 * 60 * 10;
|
|
7
|
+
const flatten = arr => [].concat(...arr);
|
|
8
|
+
const isEmoji = /[^\p{L}\p{N}\p{P}\p{Z}{\^\$}]/gu;
|
|
9
|
+
const joinLines = lines => lines.join('\n');
|
|
10
|
+
const joinPath = hops => hops.join(' → ');
|
|
11
|
+
const joinSections = sections => sections.join('\n\n');
|
|
12
|
+
const moreHops = count => `...${count} more...`;
|
|
13
|
+
const nothingPending = 'No pending payments or channels';
|
|
14
|
+
const shortKey = key => key.substring(0, 8);
|
|
15
|
+
|
|
16
|
+
/** Summarize the pending channels and in-flight HTLCs of nodes
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
[is_monochrome]: <Mute Colors Bool>
|
|
20
|
+
[max_relays]: <Maximum Relaying Peers of a Path to Show Number>
|
|
21
|
+
nodes: [{
|
|
22
|
+
closing: [{
|
|
23
|
+
[blocks_until_recovery]: <Blocks Until Pending Balance Recovers Number>
|
|
24
|
+
[close_transaction_id]: <Closing Transaction Id Hex String>
|
|
25
|
+
[peer_alias]: <Peer Alias String>
|
|
26
|
+
peer_public_key: <Peer Public Key Hex String>
|
|
27
|
+
[pending_balance]: <Pending Recovery Tokens Number>
|
|
28
|
+
}]
|
|
29
|
+
forwarding: [{
|
|
30
|
+
fee: <Routing Fee Tokens Number>
|
|
31
|
+
[in_peer_alias]: <Inbound Peer Alias String>
|
|
32
|
+
in_peer_public_key: <Inbound Peer Public Key Hex String>
|
|
33
|
+
[out_peer_alias]: <Outbound Peer Alias String>
|
|
34
|
+
out_peer_public_key: <Outbound Peer Public Key Hex String>
|
|
35
|
+
tokens: <Forwarding Tokens Number>
|
|
36
|
+
}]
|
|
37
|
+
opening: [{
|
|
38
|
+
capacity: <Channel Capacity Tokens Number>
|
|
39
|
+
is_partner_initiated: <Peer Is Opening The Channel Bool>
|
|
40
|
+
[peer_alias]: <Peer Alias String>
|
|
41
|
+
peer_public_key: <Peer Public Key Hex String>
|
|
42
|
+
transaction_id: <Funding Transaction Id Hex String>
|
|
43
|
+
}]
|
|
44
|
+
sending: [{
|
|
45
|
+
[fee]: <Routing Fee Tokens Number>
|
|
46
|
+
hops: [{
|
|
47
|
+
[alias]: <Hop Node Alias String>
|
|
48
|
+
public_key: <Hop Node Public Key Hex String>
|
|
49
|
+
}]
|
|
50
|
+
is_rebalance: <Payment Is Going Back To The Node Itself Bool>
|
|
51
|
+
tokens: <Sending Tokens Number>
|
|
52
|
+
}]
|
|
53
|
+
[alias]: <Node Alias String>
|
|
54
|
+
public_key: <Node Identity Public Key Hex String>
|
|
55
|
+
}]
|
|
56
|
+
[tagged]: [{
|
|
57
|
+
icons: [<Icon String>]
|
|
58
|
+
public_key: <Public Key Hex String>
|
|
59
|
+
}]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@returns
|
|
63
|
+
{
|
|
64
|
+
summary: <Pending Items Summary String>
|
|
65
|
+
}
|
|
66
|
+
*/
|
|
67
|
+
module.exports = ({is_monochrome, max_relays, nodes, tagged}) => {
|
|
68
|
+
// Zero or absent tokens have no amount to display
|
|
69
|
+
const amount = tokens => {
|
|
70
|
+
return formatTokens({is_monochrome, tokens}).display.trim();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Paths may collapse their relaying peers to keep the path short
|
|
74
|
+
const path = hops => {
|
|
75
|
+
const [first, ...others] = hops;
|
|
76
|
+
|
|
77
|
+
const [last, ...reversedRelays] = others.slice().reverse();
|
|
78
|
+
|
|
79
|
+
const relays = reversedRelays.reverse();
|
|
80
|
+
|
|
81
|
+
// Exit early when there is no limit or the relays are within the limit
|
|
82
|
+
if (max_relays === undefined || !others.length) {
|
|
83
|
+
return joinPath(hops);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (relays.length <= max_relays) {
|
|
87
|
+
return joinPath(hops);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const shown = relays.slice(Number(), max_relays);
|
|
91
|
+
|
|
92
|
+
const hidden = [moreHops(relays.length - shown.length)];
|
|
93
|
+
|
|
94
|
+
return joinPath([first].concat(shown).concat(hidden).concat(last));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Nodes are named with their alias and a short key
|
|
98
|
+
const named = (alias, key) => {
|
|
99
|
+
return [alias, shortKey(key)].filter(n => !!n).join(' ');
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Other nodes are named with their tag icons and no emojis of their own
|
|
103
|
+
const peer = (alias, key) => {
|
|
104
|
+
const regularAlias = (alias || String()).replace(isEmoji, String()).trim();
|
|
105
|
+
const tags = (tagged || []).find(n => n.public_key === key);
|
|
106
|
+
|
|
107
|
+
const icons = !tags ? [] : tags.icons;
|
|
108
|
+
|
|
109
|
+
return [].concat(icons).concat(named(regularAlias, key)).join(' ');
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const sections = nodes.map(node => {
|
|
113
|
+
// Channels that are waiting for their funding transaction to confirm
|
|
114
|
+
const opening = node.opening.map(channel => {
|
|
115
|
+
const direction = !channel.is_partner_initiated ? 'outbound' : 'inbound';
|
|
116
|
+
const size = amount(channel.capacity);
|
|
117
|
+
const tx = channel.transaction_id;
|
|
118
|
+
const withPeer = peer(channel.peer_alias, channel.peer_public_key);
|
|
119
|
+
|
|
120
|
+
const action = `${direction} ${size} channel with ${withPeer}`;
|
|
121
|
+
|
|
122
|
+
return `Waiting for ${action} to confirm: ${tx}`;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Channels that are closing, waiting on a timelock or on confirmation
|
|
126
|
+
const closing = node.closing.map(channel => {
|
|
127
|
+
const blocks = channel.blocks_until_recovery;
|
|
128
|
+
const funds = amount(channel.pending_balance);
|
|
129
|
+
const tx = channel.close_transaction_id;
|
|
130
|
+
const withPeer = peer(channel.peer_alias, channel.peer_public_key);
|
|
131
|
+
|
|
132
|
+
// Exit early when the funds are timelocked for some blocks
|
|
133
|
+
if (!!blocks && !!funds) {
|
|
134
|
+
const wait = formatDuration({ms: blocksAsMs(blocks)}).display;
|
|
135
|
+
|
|
136
|
+
const action = `recover ${funds} in ${wait} from closing channel`;
|
|
137
|
+
|
|
138
|
+
return `Waiting to ${action} with ${withPeer}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const confirm = !tx ? String() : `: ${tx}`;
|
|
142
|
+
const recover = `recover ${funds} from closing channel`;
|
|
143
|
+
|
|
144
|
+
const action = !funds ? 'close channel' : recover;
|
|
145
|
+
|
|
146
|
+
return `Waiting to ${action} with ${withPeer}${confirm}`;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// HTLCs that are being relayed from one peer to another
|
|
150
|
+
const forwarding = node.forwarding.map(forward => {
|
|
151
|
+
const fee = !forward.fee ? String() : ` for ${amount(forward.fee)} fee`;
|
|
152
|
+
const from = peer(forward.in_peer_alias, forward.in_peer_public_key);
|
|
153
|
+
const to = peer(forward.out_peer_alias, forward.out_peer_public_key);
|
|
154
|
+
const tokens = amount(forward.tokens);
|
|
155
|
+
|
|
156
|
+
return `Forwarding ${tokens}${fee} from ${from} to ${to}`;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Payments or probes that are going out along a path of peers
|
|
160
|
+
const sending = node.sending.map(payment => {
|
|
161
|
+
const fee = !payment.fee ? String() : ` for ${amount(payment.fee)} fee`;
|
|
162
|
+
const hops = payment.hops.map(hop => peer(hop.alias, hop.public_key));
|
|
163
|
+
const tokens = amount(payment.tokens);
|
|
164
|
+
|
|
165
|
+
// Exit early when the payment is a rebalance that comes back to self
|
|
166
|
+
if (!!payment.is_rebalance) {
|
|
167
|
+
// The final hop of a rebalance is the node itself and not a peer
|
|
168
|
+
const [ownNode, ...route] = hops.slice().reverse();
|
|
169
|
+
|
|
170
|
+
return `Rebalancing ${tokens}${fee} out ${path(route.reverse())}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return `Probing ${tokens}${fee} out ${path(hops)}`;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Sections go from the slowest to change to the fastest to change
|
|
177
|
+
return {
|
|
178
|
+
lines: flatten([closing, opening, forwarding, sending]),
|
|
179
|
+
title: named(node.alias, node.public_key),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const pending = sections.filter(n => !!n.lines.length);
|
|
184
|
+
|
|
185
|
+
// Exit early when there is nothing pending on any node
|
|
186
|
+
if (!pending.length) {
|
|
187
|
+
return {summary: nothingPending};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Exit early when there is a single node, there is no need for a title
|
|
191
|
+
if (nodes.length === [nodes].length) {
|
|
192
|
+
const [node] = pending;
|
|
193
|
+
|
|
194
|
+
return {summary: joinLines(node.lines)};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const titled = pending.map(node => {
|
|
198
|
+
const title = !!is_monochrome ? node.title : bold(node.title);
|
|
199
|
+
|
|
200
|
+
return joinLines([title].concat(node.lines));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return {summary: joinSections(titled)};
|
|
204
|
+
};
|
|
@@ -419,12 +419,17 @@ module.exports = (args, cbk) => {
|
|
|
419
419
|
return cbk([404, 'NoActiveChannelWithOutgoingPeer']);
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
422
|
+
// The balance that can be sent after the reserve and a buffer for fees
|
|
423
|
+
const spendable = channel => {
|
|
424
|
+
const htlcFee = htlcOutputSize * rate(channel);
|
|
425
|
+
const reserve = channelReserve(channel);
|
|
425
426
|
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
const fees = channel.commit_transaction_fee + htlcFee;
|
|
428
|
+
|
|
429
|
+
return floor(channel.local_balance - reserve - feeBuffer(fees));
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const withBalance = withPeer.filter(n => spendable(n) > tokens);
|
|
428
433
|
|
|
429
434
|
if (!withBalance.length) {
|
|
430
435
|
return cbk([404, 'NoOutboundPeerWithSufficientBalance']);
|
|
@@ -435,20 +440,20 @@ module.exports = (args, cbk) => {
|
|
|
435
440
|
return cbk(null, {ids: withBalance.map(n => n.id)});
|
|
436
441
|
}
|
|
437
442
|
|
|
438
|
-
// A single peer goes out through one channel with enough balance
|
|
439
443
|
const attribute = 'local_balance';
|
|
444
|
+
const required = args.find_max || tokens;
|
|
440
445
|
|
|
441
|
-
const
|
|
446
|
+
const {sorted} = sortBy({attribute, array: withBalance});
|
|
442
447
|
|
|
443
|
-
const
|
|
444
|
-
const reserve = channelReserve(channel);
|
|
448
|
+
const [largest] = sorted.slice().reverse();
|
|
445
449
|
|
|
446
|
-
|
|
447
|
-
const
|
|
450
|
+
// Use the smallest channel that can send the amount, or the largest
|
|
451
|
+
const channel = sorted.find(n => spendable(n) >= required) || largest;
|
|
448
452
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
453
|
+
return cbk(null, {
|
|
454
|
+
ids: [channel.id],
|
|
455
|
+
max: spendable(channel) || args.find_max,
|
|
456
|
+
});
|
|
452
457
|
}],
|
|
453
458
|
|
|
454
459
|
// Log sending towards destination
|
package/package.json
CHANGED
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"asyncjs-util": "1.2.12",
|
|
26
26
|
"bech32": "2.0.0",
|
|
27
27
|
"bip66": "2.0.0",
|
|
28
|
-
"bitcoinjs-lib": "6.1.7",
|
|
29
28
|
"bolt03": "2.0.0",
|
|
30
29
|
"bolt04": "1.3.0",
|
|
31
30
|
"bolt07": "2.0.0",
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
"ini": "7.0.0",
|
|
39
38
|
"inquirer": "14.2.3",
|
|
40
39
|
"ln-accounting": "10.0.8",
|
|
41
|
-
"ln-service": "60.1.
|
|
40
|
+
"ln-service": "60.1.3",
|
|
42
41
|
"ln-sync": "8.0.7",
|
|
43
42
|
"ln-telegram": "8.0.7",
|
|
44
43
|
"moment": "2.31.0",
|
|
@@ -53,7 +52,7 @@
|
|
|
53
52
|
},
|
|
54
53
|
"description": "Lightning balance CLI",
|
|
55
54
|
"devDependencies": {
|
|
56
|
-
"invoices": "6.
|
|
55
|
+
"invoices": "6.3.0",
|
|
57
56
|
"ln-docker-daemons": "8.1.0"
|
|
58
57
|
},
|
|
59
58
|
"engines": {
|
|
@@ -105,5 +104,5 @@
|
|
|
105
104
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
106
105
|
"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"
|
|
107
106
|
},
|
|
108
|
-
"version": "
|
|
107
|
+
"version": "25.0.0"
|
|
109
108
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
const {address} = require('bitcoinjs-lib');
|
|
2
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
3
3
|
const {connectPeer} = require('ln-sync');
|
|
4
4
|
const {formatTokens} = require('ln-sync');
|
|
5
5
|
const {getPeers} = require('ln-service');
|
|
6
6
|
const {getPublicKey} = require('ln-service');
|
|
7
|
+
const {idForTransaction} = require('@alexbosworth/blockchain');
|
|
7
8
|
const {maintainUtxoLocks} = require('ln-sync');
|
|
8
9
|
const {makePeerRequest} = require('paid-services');
|
|
9
|
-
const {
|
|
10
|
+
const {p2msScript} = require('@alexbosworth/blockchain');
|
|
11
|
+
const {p2wshOutputScript} = require('@alexbosworth/blockchain');
|
|
10
12
|
const {parsePaymentRequest} = require('ln-service');
|
|
11
13
|
const {pay} = require('ln-service');
|
|
12
|
-
const {payments} = require('bitcoinjs-lib');
|
|
13
14
|
const {prepareForChannelProposal} = require('ln-service');
|
|
14
15
|
const {reserveTransitFunds} = require('ln-sync');
|
|
15
16
|
const {returnResult} = require('asyncjs-util');
|
|
16
17
|
const {signTransaction} = require('ln-service');
|
|
17
|
-
const {
|
|
18
|
+
const {transactionFromComponents} = require('@alexbosworth/blockchain');
|
|
18
19
|
const {waitForPendingOpen} = require('ln-sync');
|
|
19
20
|
|
|
20
21
|
const {balancedChannelKeyTypes} = require('./service_key_types');
|
|
@@ -22,29 +23,28 @@ const {balancedChannelKeyTypes} = require('./service_key_types');
|
|
|
22
23
|
const acceptRequestIdType = '0';
|
|
23
24
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
24
25
|
const defaultMaxFeeMtokens = '9000';
|
|
25
|
-
const {fromBech32} = address;
|
|
26
|
-
const {fromHex} = Transaction;
|
|
27
26
|
const fundingAmount = (capacity, rate) => (capacity + (190 * rate)) / 2;
|
|
28
27
|
const fundingFee = rate => Math.ceil(190 / 2 * rate);
|
|
29
28
|
const giveTokens = capacity => Math.ceil(capacity / 2);
|
|
30
29
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
31
30
|
const idAsHash = id => Buffer.from(id, 'hex').reverse();
|
|
31
|
+
const inputsOf = tx => componentsOfTransaction({transaction: tx}).inputs;
|
|
32
32
|
const interval = 1000 * 15;
|
|
33
33
|
const {isArray} = Array;
|
|
34
|
+
const isSameId = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
34
35
|
const multiSigKeyFamily = 0;
|
|
35
36
|
const networkBitcoin = 'btc';
|
|
36
37
|
const networkRegtest = 'btcregtest';
|
|
37
38
|
const networkTestnet = 'btctestnet';
|
|
38
39
|
const numAsHex = num => num.toString(16);
|
|
39
40
|
const paddedHexNumber = n => n.length % 2 ? `0${n}` : n;
|
|
40
|
-
const {p2ms} = payments;
|
|
41
|
-
const {p2pkh} = payments;
|
|
42
41
|
const p2pTimeoutMs = 1000 * 90;
|
|
43
|
-
const {p2wsh} = payments;
|
|
44
42
|
const relockIntervalMs = 1000 * 20;
|
|
43
|
+
const sighashAll = 1;
|
|
45
44
|
const times = 60 * 6;
|
|
46
|
-
const {toOutputScript} = address;
|
|
47
45
|
const transitKeyFamily = 805;
|
|
46
|
+
const txLocktime = 0;
|
|
47
|
+
const txVersion = 1;
|
|
48
48
|
|
|
49
49
|
/** Accept a balanced channel
|
|
50
50
|
|
|
@@ -133,17 +133,13 @@ module.exports = (args, cbk) => {
|
|
|
133
133
|
cbk);
|
|
134
134
|
}],
|
|
135
135
|
|
|
136
|
-
//
|
|
136
|
+
// Make sure the network is supported
|
|
137
137
|
network: ['validate', ({}, cbk) => {
|
|
138
138
|
switch (args.network) {
|
|
139
139
|
case networkBitcoin:
|
|
140
|
-
return cbk(null, networks.bitcoin);
|
|
141
|
-
|
|
142
140
|
case networkRegtest:
|
|
143
|
-
return cbk(null, networks.regtest);
|
|
144
|
-
|
|
145
141
|
case networkTestnet:
|
|
146
|
-
return cbk(null,
|
|
142
|
+
return cbk(null, args.network);
|
|
147
143
|
|
|
148
144
|
default:
|
|
149
145
|
return cbk([400, 'UnsupportedNetworkForAcceptingBalancedChannel']);
|
|
@@ -200,11 +196,10 @@ module.exports = (args, cbk) => {
|
|
|
200
196
|
args.logger.info({refund_transaction: askForTransit.refund});
|
|
201
197
|
|
|
202
198
|
const keys = [getMultiSigKey.public_key, args.remote_multisig_key];
|
|
203
|
-
const tx = new Transaction();
|
|
204
199
|
|
|
205
|
-
const redeem =
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
const redeem = p2msScript({
|
|
201
|
+
keys: keys.sort().map(hexAsBuffer),
|
|
202
|
+
required: keys.length,
|
|
208
203
|
});
|
|
209
204
|
|
|
210
205
|
const remoteFunding = {
|
|
@@ -212,41 +207,48 @@ module.exports = (args, cbk) => {
|
|
|
212
207
|
vout: args.remote_tx_vout,
|
|
213
208
|
};
|
|
214
209
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
const script = multiSig.output;
|
|
210
|
+
const {script} = p2wshOutputScript({hash: redeem.hash});
|
|
218
211
|
|
|
219
212
|
// The inputs to the funding tx are the transit transactions
|
|
220
213
|
const inputs = [askForTransit, remoteFunding].map(({id, vout}) => ({
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
id,
|
|
215
|
+
vout,
|
|
216
|
+
script: String(),
|
|
223
217
|
sequence: Number(),
|
|
224
218
|
}));
|
|
225
219
|
|
|
226
220
|
// Sort the inputs for BIP 69 deterministic encoding
|
|
227
221
|
inputs.sort((a, b) => {
|
|
228
|
-
const aHash = bufferAsHex(a.
|
|
229
|
-
const bHash = bufferAsHex(b.
|
|
222
|
+
const aHash = bufferAsHex(idAsHash(a.id));
|
|
223
|
+
const bHash = bufferAsHex(idAsHash(b.id));
|
|
230
224
|
|
|
231
|
-
return aHash.localeCompare(bHash) || a.
|
|
225
|
+
return aHash.localeCompare(bHash) || a.vout - b.vout;
|
|
232
226
|
});
|
|
233
227
|
|
|
234
|
-
// Add the inputs to the channel funding transaction
|
|
235
|
-
inputs.forEach(n => tx.addInput(n.hash, n.index, n.sequence));
|
|
236
|
-
|
|
237
228
|
// The output to the channel funding is capacity paid to the 2:2 addr
|
|
238
|
-
|
|
229
|
+
const outputs = [{script: bufferAsHex(script), tokens: args.capacity}];
|
|
230
|
+
|
|
231
|
+
const {transaction} = transactionFromComponents({
|
|
232
|
+
inputs,
|
|
233
|
+
outputs,
|
|
234
|
+
locktime: txLocktime,
|
|
235
|
+
version: txVersion,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const {id} = idForTransaction({transaction});
|
|
239
239
|
|
|
240
240
|
args.logger.info({
|
|
241
|
-
funding_tx_id:
|
|
241
|
+
funding_tx_id: id,
|
|
242
242
|
waiting_for_full_channel_proposal: true,
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
return cbk(null, {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
transaction_id:
|
|
249
|
-
transaction_vout:
|
|
246
|
+
transaction,
|
|
247
|
+
pending_channel_id: bufferAsHex(redeem.hash),
|
|
248
|
+
transaction_id: id,
|
|
249
|
+
transaction_vout: outputs.findIndex(n => {
|
|
250
|
+
return n.script === bufferAsHex(script);
|
|
251
|
+
}),
|
|
250
252
|
});
|
|
251
253
|
}],
|
|
252
254
|
|
|
@@ -255,19 +257,18 @@ module.exports = (args, cbk) => {
|
|
|
255
257
|
'askForTransit',
|
|
256
258
|
'fundingTx',
|
|
257
259
|
'network',
|
|
258
|
-
({askForTransit, fundingTx
|
|
260
|
+
({askForTransit, fundingTx}, cbk) =>
|
|
259
261
|
{
|
|
260
|
-
const
|
|
262
|
+
const inputs = inputsOf(fundingTx.transaction);
|
|
261
263
|
const tokens = giveTokens(args.capacity) + fundingFee(args.fee_rate);
|
|
262
|
-
const tx = fromHex(fundingTx.transaction);
|
|
263
264
|
|
|
264
265
|
// Find the input index where the funding outpoint is being spent
|
|
265
|
-
const fundingVin =
|
|
266
|
-
if (input.
|
|
266
|
+
const fundingVin = inputs.findIndex(input => {
|
|
267
|
+
if (input.vout !== askForTransit.vout) {
|
|
267
268
|
return false;
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
return input.
|
|
271
|
+
return isSameId(input.id, askForTransit.id);
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
// Sign the channel funding transaction
|
|
@@ -278,7 +279,7 @@ module.exports = (args, cbk) => {
|
|
|
278
279
|
key_index: askForTransit.index,
|
|
279
280
|
output_script: askForTransit.output,
|
|
280
281
|
output_tokens: tokens,
|
|
281
|
-
sighash:
|
|
282
|
+
sighash: sighashAll,
|
|
282
283
|
vin: fundingVin,
|
|
283
284
|
witness_script: askForTransit.script,
|
|
284
285
|
}],
|