balanceofsatoshis 25.2.2 → 25.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 +8 -1
- package/balances/get_liquidity.js +1 -13
- package/bos +1 -4
- package/chain/anchor_outcome.js +23 -0
- package/chain/anchor_output.js +86 -0
- package/chain/anchor_output_script.js +43 -0
- package/chain/close_fee_payer.js +48 -0
- package/chain/close_resolutions.js +114 -0
- package/chain/closing_fees.js +125 -18
- package/chain/contract_outputs.js +30 -0
- package/chain/funded_outputs.js +58 -0
- package/chain/funding_keys.js +59 -0
- package/chain/get_block_transactions.js +100 -0
- package/chain/get_channel_closes.js +151 -52
- package/chain/get_close_from_spend.js +125 -0
- package/chain/get_close_transactions.js +116 -0
- package/chain/get_closes_in_blocks.js +81 -0
- package/chain/get_spending_transaction.js +90 -0
- package/chain/input_weight.js +43 -0
- package/chain/is_contract_spend.js +26 -0
- package/chain/link_payment_stages.js +119 -0
- package/chain/payment_outcome.js +28 -0
- package/chain/resolution_fee.js +86 -0
- package/chain/split_fee.js +40 -0
- package/chain/split_utxos.js +167 -54
- package/chain/transaction_fee.js +56 -0
- package/chain/utxo_split_plan.js +142 -0
- package/package.json +2 -3
- package/chain/get_channel_resolution.js +0 -249
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
-
## 25.
|
|
3
|
+
## 25.3.0
|
|
4
|
+
|
|
5
|
+
- `closed`: Avoid using external chain data lookups to derive resolution info
|
|
6
|
+
- `closed`: Include anchor and HTLC resolution fees in closing fees paid
|
|
7
|
+
- `closed`: Pro-rate the fees of sweeps that are shared with other channels
|
|
8
|
+
- `closed`: Count the value of an anchor output that was not swept as a fee
|
|
9
|
+
|
|
10
|
+
## 25.2.3
|
|
4
11
|
|
|
5
12
|
- `telegram`: Indicate when a cooperative close was initiated by the peer
|
|
6
13
|
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {getChannels} = require('ln-service');
|
|
3
2
|
const {getLiquidity} = require('ln-sync');
|
|
4
|
-
const {getNode} = require('ln-service');
|
|
5
3
|
const {returnResult} = require('asyncjs-util');
|
|
6
4
|
|
|
7
5
|
const balanceFromTokens = require('./balance_from_tokens');
|
|
8
6
|
const {getTags} = require('./../tags');
|
|
9
7
|
|
|
10
|
-
const {round} = Math;
|
|
11
|
-
const topPercentile = 0.9;
|
|
12
|
-
|
|
13
8
|
/** Get the channel available liquidity
|
|
14
9
|
|
|
15
|
-
A request function is required when min_node_score is specified
|
|
16
|
-
|
|
17
10
|
{
|
|
18
11
|
[above]: <Tokens Above Tokens Number>
|
|
19
12
|
[below]: <Tokens Below Tokens Number>
|
|
@@ -24,7 +17,6 @@ const topPercentile = 0.9;
|
|
|
24
17
|
[is_top]: <Return Top Liquidity Bool>
|
|
25
18
|
lnd: <Authenticated LND API Object>
|
|
26
19
|
[max_fee_rate]: <Max Inbound Fee Rate Parts Per Million Number>
|
|
27
|
-
[request]: <Request Function>
|
|
28
20
|
[with]: <Liquidity With Public Key Hex or Tag Alias or Id String>
|
|
29
21
|
}
|
|
30
22
|
|
|
@@ -50,10 +42,6 @@ module.exports = (args, cbk) => {
|
|
|
50
42
|
return cbk([400, 'ExpectedLndToGetLiquidity']);
|
|
51
43
|
}
|
|
52
44
|
|
|
53
|
-
if (!!args.min_node_score && !args.request) {
|
|
54
|
-
return cbk([400, 'ExpectedRequestFunctionToFilterByNodeScore']);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
45
|
return cbk();
|
|
58
46
|
},
|
|
59
47
|
|
|
@@ -107,7 +95,7 @@ module.exports = (args, cbk) => {
|
|
|
107
95
|
is_outbound: args.is_outbound,
|
|
108
96
|
is_top: args.is_top,
|
|
109
97
|
lnd: args.lnd,
|
|
110
|
-
|
|
98
|
+
max_fee_rate: args.max_fee_rate,
|
|
111
99
|
with: withNodes,
|
|
112
100
|
},
|
|
113
101
|
cbk);
|
package/bos
CHANGED
|
@@ -490,8 +490,7 @@ prog
|
|
|
490
490
|
|
|
491
491
|
// Determine the outcomes of channel closings
|
|
492
492
|
.command('closed', 'Get the status of a channel closings')
|
|
493
|
-
.help('Channel closes with
|
|
494
|
-
.help('Privacy note: this requests tx data from third parties')
|
|
493
|
+
.help('Channel closes with resolution details of the closing outputs')
|
|
495
494
|
.option('--limit [limit]', 'Limit of closings to get', INT, 20)
|
|
496
495
|
.option('--no-color', 'Mute all colors')
|
|
497
496
|
.option('--node <node_name>', 'Get channel closes from saved node')
|
|
@@ -501,7 +500,6 @@ prog
|
|
|
501
500
|
return chain.getChannelCloses({
|
|
502
501
|
limit: options.limit,
|
|
503
502
|
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
|
504
|
-
request: commands.simpleRequest,
|
|
505
503
|
},
|
|
506
504
|
responses.returnObject({logger, reject, resolve}));
|
|
507
505
|
} catch (err) {
|
|
@@ -995,7 +993,6 @@ prog
|
|
|
995
993
|
is_top: options.top || undefined,
|
|
996
994
|
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
|
997
995
|
max_fee_rate: options.maxFeeRate || undefined,
|
|
998
|
-
request: commands.simpleRequest,
|
|
999
996
|
with: options.with,
|
|
1000
997
|
},
|
|
1001
998
|
responses.returnNumber({logger, reject, resolve, number: 'balance'}));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Describe the outcome of a closed channel anchor output
|
|
2
|
+
|
|
3
|
+
{
|
|
4
|
+
[is_confirmed]: <Anchor Output Spend Is Confirmed Bool>
|
|
5
|
+
[is_pending]: <Anchor Output Spend Is Pending Confirmation Bool>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@returns
|
|
9
|
+
{
|
|
10
|
+
outcome: <Anchor Outcome String>
|
|
11
|
+
}
|
|
12
|
+
*/
|
|
13
|
+
module.exports = args => {
|
|
14
|
+
if (!!args.is_confirmed) {
|
|
15
|
+
return {outcome: 'swept'};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!!args.is_pending) {
|
|
19
|
+
return {outcome: 'pending'};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {outcome: 'unclaimed'};
|
|
23
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
const anchorOutputScript = require('./anchor_output_script');
|
|
4
|
+
const fundingKeys = require('./funding_keys');
|
|
5
|
+
|
|
6
|
+
/** Find our anchor output in a close transaction
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
[anchor_vout]: <LND Reported Anchor Output Index Number>
|
|
10
|
+
[close_balance_spent_by]: <Close Balance Spent By Id Hex String>
|
|
11
|
+
[close_balance_vout]: <Close Balance Output Index Number>
|
|
12
|
+
close_payments: [{
|
|
13
|
+
transaction_id: <HTLC Transaction Id Hex String>
|
|
14
|
+
transaction_vout: <HTLC Transaction Output Index Number>
|
|
15
|
+
}]
|
|
16
|
+
close_transaction_id: <Close Transaction Id Hex String>
|
|
17
|
+
[is_cooperative_close]: <Channel Was Cooperatively Closed Bool>
|
|
18
|
+
[transaction]: <Raw Close Transaction Hex String>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@returns
|
|
22
|
+
{
|
|
23
|
+
[vout]: <Anchor Output Index Number>
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = args => {
|
|
27
|
+
// Exit early when LND reported the anchor output
|
|
28
|
+
if (args.anchor_vout !== undefined) {
|
|
29
|
+
return {vout: args.anchor_vout};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Exit early when there are no anchors in a cooperative close
|
|
33
|
+
if (!!args.is_cooperative_close || !args.transaction) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {inputs, outputs} = componentsOfTransaction({
|
|
38
|
+
transaction: args.transaction,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const [fundingSpend] = inputs;
|
|
42
|
+
|
|
43
|
+
const {keys} = fundingKeys({witness: fundingSpend.witness});
|
|
44
|
+
|
|
45
|
+
// Exit early when the funding keys that anchors are derived from are unknown
|
|
46
|
+
if (!keys) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const anchorScripts = keys.map(key => anchorOutputScript({key}).script);
|
|
51
|
+
|
|
52
|
+
// Outputs that resolved as the balance or as HTLCs are not anchors
|
|
53
|
+
const htlcVouts = args.close_payments
|
|
54
|
+
.filter(n => n.transaction_id === args.close_transaction_id)
|
|
55
|
+
.map(n => n.transaction_vout);
|
|
56
|
+
|
|
57
|
+
const balanceVout = args.close_balance_vout;
|
|
58
|
+
|
|
59
|
+
const knownVouts = []
|
|
60
|
+
.concat(balanceVout !== undefined ? [balanceVout] : [])
|
|
61
|
+
.concat(htlcVouts);
|
|
62
|
+
|
|
63
|
+
const anchors = outputs
|
|
64
|
+
.map(({script, tokens}, vout) => ({script, tokens, vout}))
|
|
65
|
+
.filter(n => !knownVouts.includes(n.vout))
|
|
66
|
+
.filter(n => anchorScripts.includes(n.script));
|
|
67
|
+
|
|
68
|
+
// Exit early when there are no anchor outputs
|
|
69
|
+
if (!anchors.length) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [anchor, otherAnchor] = anchors;
|
|
74
|
+
|
|
75
|
+
// Exit early when both sides have an anchor, one of them is ours
|
|
76
|
+
if (!!otherAnchor) {
|
|
77
|
+
return {vout: anchor.vout};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Exit early when a single anchor is ours as we had outputs to protect
|
|
81
|
+
if (!!args.close_balance_spent_by || !!args.close_payments.length) {
|
|
82
|
+
return {vout: anchor.vout};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {};
|
|
86
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const {createHash} = require('node:crypto');
|
|
2
|
+
|
|
3
|
+
const {p2wshOutputScript} = require('@alexbosworth/blockchain');
|
|
4
|
+
const {scriptElementsAsScript} = require('@alexbosworth/blockchain');
|
|
5
|
+
|
|
6
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
7
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
8
|
+
const OP_16 = 96;
|
|
9
|
+
const OP_CHECKSEQUENCEVERIFY = 178;
|
|
10
|
+
const OP_CHECKSIG = 172;
|
|
11
|
+
const OP_ENDIF = 104;
|
|
12
|
+
const OP_IFDUP = 115;
|
|
13
|
+
const OP_NOTIF = 100;
|
|
14
|
+
const sha256 = preimage => createHash('sha256').update(preimage).digest();
|
|
15
|
+
|
|
16
|
+
/** Derive the anchor output script for a funding public key
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
key: <Funding Public Key Hex String>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@returns
|
|
23
|
+
{
|
|
24
|
+
script: <Anchor P2WSH Output Script Hex String>
|
|
25
|
+
}
|
|
26
|
+
*/
|
|
27
|
+
module.exports = ({key}) => {
|
|
28
|
+
const {script} = scriptElementsAsScript({
|
|
29
|
+
elements: [
|
|
30
|
+
hexAsBuffer(key),
|
|
31
|
+
OP_CHECKSIG,
|
|
32
|
+
OP_IFDUP,
|
|
33
|
+
OP_NOTIF,
|
|
34
|
+
OP_16,
|
|
35
|
+
OP_CHECKSEQUENCEVERIFY,
|
|
36
|
+
OP_ENDIF,
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const hash = sha256(hexAsBuffer(script));
|
|
41
|
+
|
|
42
|
+
return {script: bufferAsHex(p2wshOutputScript({hash}).script)};
|
|
43
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
const simpleCloseSequence = 0xfffffffd;
|
|
4
|
+
|
|
5
|
+
/** Determine which side paid the fee of a channel close transaction
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
[is_cooperative_close]: <Channel Was Cooperatively Closed Bool>
|
|
9
|
+
[is_partner_closed]: <Channel Was Closed By Peer Bool>
|
|
10
|
+
[is_partner_initiated]: <Channel Was Opened By Peer Bool>
|
|
11
|
+
[transaction]: <Raw Close Transaction Hex String>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@returns
|
|
15
|
+
{
|
|
16
|
+
[is_peer_paying]: <Peer Paid The Close Transaction Fee Bool>
|
|
17
|
+
}
|
|
18
|
+
*/
|
|
19
|
+
module.exports = args => {
|
|
20
|
+
const isPeerClosed = args.is_partner_closed;
|
|
21
|
+
const isPeerOpened = args.is_partner_initiated;
|
|
22
|
+
|
|
23
|
+
// Exit early when the channel was force closed, the opener paid for that
|
|
24
|
+
if (!args.is_cooperative_close) {
|
|
25
|
+
return {is_peer_paying: isPeerOpened};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isOpenerClosing = isPeerOpened === isPeerClosed;
|
|
29
|
+
|
|
30
|
+
// Exit early when the opener also closed, they paid in either protocol
|
|
31
|
+
if (isPeerOpened !== undefined && isOpenerClosing) {
|
|
32
|
+
return {is_peer_paying: isPeerOpened};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Exit early when the close protocol is unknown without the close tx
|
|
36
|
+
if (!args.transaction) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const {inputs} = componentsOfTransaction({transaction: args.transaction});
|
|
41
|
+
|
|
42
|
+
const [fundingSpend] = inputs;
|
|
43
|
+
|
|
44
|
+
// The closer pays for a simple close, otherwise the opener pays
|
|
45
|
+
const isSimpleClose = fundingSpend.sequence === simpleCloseSequence;
|
|
46
|
+
|
|
47
|
+
return {is_peer_paying: isSimpleClose ? isPeerClosed : isPeerOpened};
|
|
48
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
const anchorOutcome = require('./anchor_outcome');
|
|
4
|
+
const linkPaymentStages = require('./link_payment_stages');
|
|
5
|
+
const paymentOutcome = require('./payment_outcome');
|
|
6
|
+
const {sortBy} = require('./../arrays');
|
|
7
|
+
|
|
8
|
+
const sortByVout = arr => sortBy({array: arr, attribute: 'vout'}).sorted;
|
|
9
|
+
|
|
10
|
+
/** Derive the resolutions of closed channel outputs from LND close details
|
|
11
|
+
|
|
12
|
+
Output values for balance and anchor outputs are only known when the close
|
|
13
|
+
transaction is present in the wallet transactions
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
[anchor_is_confirmed]: <Anchor Output Spend Is Confirmed Bool>
|
|
17
|
+
[anchor_is_pending]: <Anchor Output Spend Is Pending Confirmation Bool>
|
|
18
|
+
[anchor_spent_by]: <Anchor Sweep Transaction Id Hex String>
|
|
19
|
+
[anchor_vout]: <Anchor Output Index Number>
|
|
20
|
+
[close_balance_spent_by]: <Balance Sweep Transaction Id Hex String>
|
|
21
|
+
[close_balance_vout]: <Balance Output Index Number>
|
|
22
|
+
close_payments: [{
|
|
23
|
+
is_outgoing: <Payment Is Outgoing Bool>
|
|
24
|
+
is_paid: <Payment Is Claimed With Preimage Bool>
|
|
25
|
+
is_pending: <Payment Resolution Is Pending Bool>
|
|
26
|
+
is_refunded: <Payment Timed Out And Went Back To Payer Bool>
|
|
27
|
+
[spent_by]: <Close Transaction Spent By Transaction Id Hex String>
|
|
28
|
+
tokens: <Associated Tokens Number>
|
|
29
|
+
transaction_id: <Transaction Id Hex String>
|
|
30
|
+
transaction_vout: <Transaction Output Index Number>
|
|
31
|
+
}]
|
|
32
|
+
close_transaction_id: <Close Transaction Id Hex String>
|
|
33
|
+
transactions: [{
|
|
34
|
+
id: <Transaction Id Hex String>
|
|
35
|
+
[transaction]: <Raw Transaction Hex String>
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@returns
|
|
40
|
+
{
|
|
41
|
+
resolutions: [{
|
|
42
|
+
[transaction_id]: <Latest Resolving Transaction Id Hex String>
|
|
43
|
+
type: <Resolution Type String>
|
|
44
|
+
[value]: <Output Value Tokens Number>
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
*/
|
|
48
|
+
module.exports = args => {
|
|
49
|
+
// Output values are only known when the close tx is in the wallet
|
|
50
|
+
const [closeTx] = args.transactions
|
|
51
|
+
.filter(n => !!n.transaction)
|
|
52
|
+
.filter(n => n.id === args.close_transaction_id)
|
|
53
|
+
.map(({transaction}) => componentsOfTransaction({transaction}));
|
|
54
|
+
|
|
55
|
+
const outputs = !!closeTx ? closeTx.outputs : [];
|
|
56
|
+
|
|
57
|
+
const tokensAt = vout => !outputs[vout] ? undefined : outputs[vout].tokens;
|
|
58
|
+
|
|
59
|
+
const anchorResolution = anchorOutcome({
|
|
60
|
+
is_confirmed: args.anchor_is_confirmed,
|
|
61
|
+
is_pending: args.anchor_is_pending,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const anchor = {
|
|
65
|
+
transaction_id: args.anchor_spent_by,
|
|
66
|
+
type: `anchor_${anchorResolution.outcome}`,
|
|
67
|
+
value: tokensAt(args.anchor_vout),
|
|
68
|
+
vout: args.anchor_vout,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const balance = {
|
|
72
|
+
transaction_id: args.close_balance_spent_by,
|
|
73
|
+
type: 'balance_swept',
|
|
74
|
+
value: tokensAt(args.close_balance_vout),
|
|
75
|
+
vout: args.close_balance_vout,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// HTLCs resolved over two stages are linked to get their final outcome
|
|
79
|
+
const {payments} = linkPaymentStages({
|
|
80
|
+
close_payments: args.close_payments,
|
|
81
|
+
transactions: args.transactions,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const htlcs = payments.map(payment => {
|
|
85
|
+
const direction = !!payment.is_outgoing ? 'outgoing' : 'incoming';
|
|
86
|
+
|
|
87
|
+
const {outcome} = paymentOutcome({
|
|
88
|
+
is_paid: payment.is_paid,
|
|
89
|
+
is_pending: payment.is_pending,
|
|
90
|
+
is_refunded: payment.is_refunded,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
transaction_id: payment.spent_by,
|
|
95
|
+
type: `${direction}_htlc_${outcome}`,
|
|
96
|
+
value: payment.tokens,
|
|
97
|
+
vout: payment.transaction_vout,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Anchor and balance resolutions are only present when LND reported them
|
|
102
|
+
const resolutions = []
|
|
103
|
+
.concat(args.anchor_vout !== undefined ? [anchor] : [])
|
|
104
|
+
.concat(!!args.close_balance_spent_by ? [balance] : [])
|
|
105
|
+
.concat(htlcs);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
resolutions: sortByVout(resolutions).map(resolution => ({
|
|
109
|
+
transaction_id: resolution.transaction_id || undefined,
|
|
110
|
+
type: resolution.type,
|
|
111
|
+
value: resolution.value,
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
};
|
package/chain/closing_fees.js
CHANGED
|
@@ -1,51 +1,158 @@
|
|
|
1
1
|
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
2
|
|
|
3
|
+
const closeFeePayer = require('./close_fee_payer');
|
|
4
|
+
const linkPaymentStages = require('./link_payment_stages');
|
|
5
|
+
const resolutionFee = require('./resolution_fee');
|
|
6
|
+
|
|
7
|
+
const outputsOf = tx => componentsOfTransaction({transaction: tx}).outputs;
|
|
3
8
|
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
9
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
4
10
|
|
|
5
11
|
/** Calculate channel closing fees paid
|
|
6
12
|
|
|
13
|
+
Fees are only returned when every fee that may have been paid is known.
|
|
14
|
+
|
|
15
|
+
Fees include the close transaction fee when it was paid by this side, the
|
|
16
|
+
fees of our own transactions that resolve the outputs of the close, and the
|
|
17
|
+
value of an anchor output that was never swept and so is lost.
|
|
18
|
+
|
|
19
|
+
When a resolving transaction also spends the outputs of other channels, the
|
|
20
|
+
fee is pro-rated by the weight of the inputs spending this channel's outputs.
|
|
21
|
+
|
|
7
22
|
{
|
|
23
|
+
[anchor_is_confirmed]: <Anchor Sweep Is Confirmed Bool>
|
|
24
|
+
[anchor_is_pending]: <Anchor Sweep Is Pending Confirmation Bool>
|
|
25
|
+
[anchor_spent_by]: <Anchor Sweep Transaction Id Hex String>
|
|
26
|
+
[anchor_vout]: <Anchor Output Index Number>
|
|
8
27
|
[capacity]: <Channel Capacity Tokens Number>
|
|
9
28
|
[close_balance_spent_by]: <Close Balance Spent By Id Hex String>
|
|
29
|
+
close_payments: [{
|
|
30
|
+
is_outgoing: <Payment Is Outgoing Bool>
|
|
31
|
+
is_paid: <Payment Is Claimed With Preimage Bool>
|
|
32
|
+
is_pending: <Payment Resolution Is Pending Bool>
|
|
33
|
+
is_refunded: <Payment Timed Out And Went Back To Payer Bool>
|
|
34
|
+
[spent_by]: <Spent By Transaction Id Hex String>
|
|
35
|
+
tokens: <Associated Tokens Number>
|
|
36
|
+
transaction_id: <Transaction Id Hex String>
|
|
37
|
+
transaction_vout: <Transaction Output Index Number>
|
|
38
|
+
}]
|
|
10
39
|
close_transaction_id: <Close Transaction Id Hex String>
|
|
11
|
-
|
|
40
|
+
[is_cooperative_close]: <Channel Was Cooperatively Closed Bool>
|
|
41
|
+
[is_partner_closed]: <Channel Was Closed By Peer Bool>
|
|
42
|
+
[is_partner_initiated]: <Channel is Initiated By Peer Bool>
|
|
43
|
+
other_contract_outputs: [{
|
|
44
|
+
id: <Other Channel Contract Transaction Id Hex String>
|
|
45
|
+
[vout]: <Other Channel Contract Transaction Output Index Number>
|
|
46
|
+
}]
|
|
12
47
|
transactions: [{
|
|
13
48
|
[fee]: <Paid Transaction Fee Tokens Number>
|
|
49
|
+
id: <Transaction Id Hex String>
|
|
14
50
|
[transaction]: <Raw Transaction Hex String>
|
|
15
51
|
}]
|
|
16
52
|
}
|
|
17
53
|
|
|
18
54
|
@returns
|
|
19
55
|
{
|
|
20
|
-
fees: <Closing Fees Paid Number>
|
|
56
|
+
[fees]: <Known Closing Fees Paid Tokens Number>
|
|
21
57
|
}
|
|
22
58
|
*/
|
|
23
59
|
module.exports = args => {
|
|
24
|
-
const
|
|
60
|
+
const [closeTx] = args.transactions
|
|
25
61
|
.filter(n => !!n.transaction)
|
|
26
|
-
.
|
|
62
|
+
.filter(n => n.id === args.close_transaction_id);
|
|
63
|
+
|
|
64
|
+
const closeTransaction = !!closeTx ? closeTx.transaction : undefined;
|
|
65
|
+
|
|
66
|
+
const payer = closeFeePayer({
|
|
67
|
+
is_cooperative_close: args.is_cooperative_close,
|
|
68
|
+
is_partner_closed: args.is_partner_closed,
|
|
69
|
+
is_partner_initiated: args.is_partner_initiated,
|
|
70
|
+
transaction: closeTransaction,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Exit early when it is not known which side paid the close tx fee
|
|
74
|
+
if (payer.is_peer_paying === undefined) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Payments resolved over two stages are linked to find their final status
|
|
79
|
+
const {payments} = linkPaymentStages({
|
|
80
|
+
close_payments: args.close_payments,
|
|
81
|
+
transactions: args.transactions,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Exit early when a resolution is still pending, fees are not yet final
|
|
85
|
+
if (!!args.anchor_is_pending || !!payments.find(n => !!n.is_pending)) {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Close outputs are spent in resolving transactions that may be our own
|
|
90
|
+
const ownSpends = args.close_payments
|
|
91
|
+
.filter(n => !!n.spent_by)
|
|
92
|
+
.filter(payment => {
|
|
93
|
+
// Exit early when the spend is a first stage second level transaction
|
|
94
|
+
if (!!payment.is_pending) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return !!payment.is_outgoing ? !!payment.is_refunded : !!payment.is_paid;
|
|
99
|
+
})
|
|
100
|
+
.map(n => n.spent_by);
|
|
101
|
+
|
|
102
|
+
const resolvingIds = uniq([]
|
|
103
|
+
.concat(!!args.close_balance_spent_by ? [args.close_balance_spent_by] : [])
|
|
104
|
+
.concat(!!args.anchor_spent_by ? [args.anchor_spent_by] : [])
|
|
105
|
+
.concat(ownSpends));
|
|
27
106
|
|
|
28
|
-
|
|
29
|
-
|
|
107
|
+
// Close tx outputs and specific second level tx outputs are this channel's
|
|
108
|
+
const contractOutputs = []
|
|
109
|
+
.concat({id: args.close_transaction_id})
|
|
110
|
+
.concat(args.close_payments.map(payment => ({
|
|
111
|
+
id: payment.transaction_id,
|
|
112
|
+
vout: payment.transaction_vout,
|
|
113
|
+
})));
|
|
30
114
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
115
|
+
const resolvingFees = resolvingIds.map(id => {
|
|
116
|
+
return resolutionFee({
|
|
117
|
+
id,
|
|
118
|
+
contract_outputs: contractOutputs,
|
|
119
|
+
other_contract_outputs: args.other_contract_outputs,
|
|
120
|
+
transactions: args.transactions,
|
|
121
|
+
}).fee;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Exit early when the fee of a resolving transaction is not known
|
|
125
|
+
if (resolvingFees.includes(undefined)) {
|
|
126
|
+
return {};
|
|
34
127
|
}
|
|
35
128
|
|
|
36
|
-
|
|
37
|
-
|
|
129
|
+
const closeOutputs = !closeTransaction ? [] : outputsOf(closeTransaction);
|
|
130
|
+
|
|
131
|
+
const anchor = closeOutputs[args.anchor_vout];
|
|
132
|
+
|
|
133
|
+
// An anchor output that was never swept is lost and counted as a fee
|
|
134
|
+
const isAnchorLost = args.anchor_vout !== undefined && !args.anchor_spent_by;
|
|
135
|
+
|
|
136
|
+
// Exit early when the value of a lost anchor output is not known
|
|
137
|
+
if (!!isAnchorLost && !anchor) {
|
|
38
138
|
return {};
|
|
39
139
|
}
|
|
40
140
|
|
|
41
|
-
|
|
42
|
-
const {outputs} = componentsOfTransaction({transaction: closer.transaction});
|
|
141
|
+
const lostTokens = !isAnchorLost ? Number() : anchor.tokens;
|
|
43
142
|
|
|
44
|
-
const
|
|
143
|
+
const resolvingTokens = sumOf(resolvingFees) + lostTokens;
|
|
144
|
+
|
|
145
|
+
// Exit early when the peer paid the close tx fee
|
|
146
|
+
if (!!payer.is_peer_paying) {
|
|
147
|
+
return {fees: resolvingTokens};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Exit early when the close tx fee was paid but the close tx is not known
|
|
151
|
+
if (!args.capacity || !closeTransaction) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
45
154
|
|
|
46
|
-
const
|
|
47
|
-
.concat(args.capacity - outValue)
|
|
48
|
-
.concat(!!balanceSpend ? balanceSpend.fee : Number());
|
|
155
|
+
const closeFee = args.capacity - sumOf(closeOutputs.map(n => n.tokens));
|
|
49
156
|
|
|
50
|
-
return {fees:
|
|
157
|
+
return {fees: closeFee + resolvingTokens};
|
|
51
158
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Derive the contract outputs of a channel from its close details
|
|
2
|
+
|
|
3
|
+
The outputs of the close transaction all belong to the channel, while HTLC
|
|
4
|
+
outputs are referred to specifically as a second level transaction may be
|
|
5
|
+
shared with other channels.
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
[close_transaction_id]: <Close Transaction Id Hex String>
|
|
9
|
+
payments: [{
|
|
10
|
+
transaction_id: <HTLC Transaction Id Hex String>
|
|
11
|
+
transaction_vout: <HTLC Transaction Output Index Number>
|
|
12
|
+
}]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@returns
|
|
16
|
+
[{
|
|
17
|
+
id: <Contract Transaction Id Hex String>
|
|
18
|
+
[vout]: <Contract Transaction Output Index Number>
|
|
19
|
+
}]
|
|
20
|
+
*/
|
|
21
|
+
module.exports = args => {
|
|
22
|
+
const id = args.close_transaction_id;
|
|
23
|
+
|
|
24
|
+
const closeOutputs = !id ? [] : [{id}];
|
|
25
|
+
|
|
26
|
+
return closeOutputs.concat(args.payments.map(payment => ({
|
|
27
|
+
id: payment.transaction_id,
|
|
28
|
+
vout: payment.transaction_vout,
|
|
29
|
+
})));
|
|
30
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const splitFee = require('./split_fee');
|
|
2
|
+
|
|
3
|
+
const minimumOutputCount = 1;
|
|
4
|
+
|
|
5
|
+
/** Count the split outputs that selected inputs can fund to reach a count
|
|
6
|
+
|
|
7
|
+
Every counted utxo that is spent has to be replaced by a new utxo
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
counted: <Spending Counted UTXOs Count Number>
|
|
11
|
+
inputs: <Spending Inputs Count Number>
|
|
12
|
+
needed: <Needed Additional UTXOs Count Number>
|
|
13
|
+
rate: <Fee Tokens Per Virtual Byte Number>
|
|
14
|
+
size: <Split Output Tokens Number>
|
|
15
|
+
tokens: <Spending Inputs Tokens Number>
|
|
16
|
+
vbytes: <Spending Inputs Virtual Bytes Number>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@returns
|
|
20
|
+
{
|
|
21
|
+
[outputs]: <Split Outputs Count Number>
|
|
22
|
+
}
|
|
23
|
+
*/
|
|
24
|
+
module.exports = args => {
|
|
25
|
+
const outputs = args.needed + args.counted;
|
|
26
|
+
|
|
27
|
+
// When the change is large enough it can be one of the new utxos
|
|
28
|
+
const withChange = outputs - minimumOutputCount;
|
|
29
|
+
|
|
30
|
+
const changeFee = splitFee({
|
|
31
|
+
has_change: true,
|
|
32
|
+
inputs: args.inputs,
|
|
33
|
+
outputs: withChange,
|
|
34
|
+
rate: args.rate,
|
|
35
|
+
vbytes: args.vbytes,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const change = args.tokens - (withChange * args.size) - changeFee;
|
|
39
|
+
|
|
40
|
+
// Exit early when the change will be large enough to count as a new utxo
|
|
41
|
+
if (withChange >= minimumOutputCount && change >= args.size) {
|
|
42
|
+
return {outputs: withChange};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fee = splitFee({
|
|
46
|
+
outputs,
|
|
47
|
+
inputs: args.inputs,
|
|
48
|
+
rate: args.rate,
|
|
49
|
+
vbytes: args.vbytes,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Exit early when the inputs cannot pay for all of the outputs
|
|
53
|
+
if (args.tokens < (outputs * args.size) + fee) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {outputs};
|
|
58
|
+
};
|