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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncMapSeries = require('async/mapSeries');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const getBlockTransactions = require('./get_block_transactions');
|
|
6
|
+
|
|
7
|
+
const flatten = arr => [].concat(...arr);
|
|
8
|
+
const {isArray} = Array;
|
|
9
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
10
|
+
|
|
11
|
+
/** Get close transactions out of the blocks they were confirmed in
|
|
12
|
+
|
|
13
|
+
When a block can't be retrieved, its close transactions are not returned
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
channels: [{
|
|
17
|
+
close_confirm_height: <Close Confirmed At Height Number>
|
|
18
|
+
close_transaction_id: <Close Transaction Id Hex String>
|
|
19
|
+
}]
|
|
20
|
+
lnd: <Authenticated LND API Object>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@returns via cbk or Promise
|
|
24
|
+
{
|
|
25
|
+
transactions: [{
|
|
26
|
+
id: <Close Transaction Id Hex String>
|
|
27
|
+
transaction: <Raw Close Transaction Hex String>
|
|
28
|
+
}]
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
31
|
+
module.exports = (args, cbk) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
return asyncAuto({
|
|
34
|
+
// Check arguments
|
|
35
|
+
validate: cbk => {
|
|
36
|
+
if (!isArray(args.channels)) {
|
|
37
|
+
return cbk([400, 'ExpectedChannelsToGetClosesInBlocks']);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!args.lnd) {
|
|
41
|
+
return cbk([400, 'ExpectedLndToGetClosesInBlocks']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return cbk();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Get the close transactions out of each block
|
|
48
|
+
getBlocks: ['validate', ({}, cbk) => {
|
|
49
|
+
// Closes that confirmed in the same block share a single block fetch
|
|
50
|
+
const heights = uniq(args.channels.map(n => n.close_confirm_height));
|
|
51
|
+
|
|
52
|
+
return asyncMapSeries(heights, (height, cbk) => {
|
|
53
|
+
const ids = args.channels
|
|
54
|
+
.filter(n => n.close_confirm_height === height)
|
|
55
|
+
.map(n => n.close_transaction_id);
|
|
56
|
+
|
|
57
|
+
return getBlockTransactions({
|
|
58
|
+
height,
|
|
59
|
+
ids,
|
|
60
|
+
lnd: args.lnd,
|
|
61
|
+
},
|
|
62
|
+
(err, res) => {
|
|
63
|
+
// Exit early when the block can't be retrieved, leaving it unknown
|
|
64
|
+
if (!!err) {
|
|
65
|
+
return cbk(null, []);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return cbk(null, res.transactions);
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
cbk);
|
|
72
|
+
}],
|
|
73
|
+
|
|
74
|
+
// Close transactions found in the blocks
|
|
75
|
+
transactions: ['getBlocks', ({getBlocks}, cbk) => {
|
|
76
|
+
return cbk(null, {transactions: flatten(getBlocks)});
|
|
77
|
+
}],
|
|
78
|
+
},
|
|
79
|
+
returnResult({reject, resolve, of: 'transactions'}, cbk));
|
|
80
|
+
});
|
|
81
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
const {subscribeToChainSpend} = require('ln-service');
|
|
4
|
+
|
|
5
|
+
const defaultTimeoutMs = 1000 * 10;
|
|
6
|
+
|
|
7
|
+
/** Get the transaction that spent an output using the LND chain notifier
|
|
8
|
+
|
|
9
|
+
When the spend is not found before the timeout, no transaction is returned
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
lnd: <Authenticated LND API Object>
|
|
13
|
+
min_height: <Look For The Spend Starting At Block Height Number>
|
|
14
|
+
output_script: <Spent Output Script Hex String>
|
|
15
|
+
[timeout]: <Stop Looking For The Spend After Milliseconds Number>
|
|
16
|
+
transaction_id: <Spent Output Transaction Id Hex String>
|
|
17
|
+
transaction_vout: <Spent Output Transaction Output Index Number>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@returns via cbk or Promise
|
|
21
|
+
{
|
|
22
|
+
[transaction]: <Raw Spending Transaction Hex String>
|
|
23
|
+
}
|
|
24
|
+
*/
|
|
25
|
+
module.exports = (args, cbk) => {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
return asyncAuto({
|
|
28
|
+
// Check arguments
|
|
29
|
+
validate: cbk => {
|
|
30
|
+
if (!args.lnd) {
|
|
31
|
+
return cbk([400, 'ExpectedLndToGetSpendingTransaction']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!args.min_height) {
|
|
35
|
+
return cbk([400, 'ExpectedMinHeightToGetSpendingTransaction']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!args.output_script) {
|
|
39
|
+
return cbk([400, 'ExpectedOutputScriptToGetSpendingTransaction']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!args.transaction_id) {
|
|
43
|
+
return cbk([400, 'ExpectedTransactionIdToGetSpendingTransaction']);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (args.transaction_vout === undefined) {
|
|
47
|
+
return cbk([400, 'ExpectedTransactionVoutToGetSpendingTransaction']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return cbk();
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Wait for LND to find the spend of the output
|
|
54
|
+
getSpend: ['validate', ({}, cbk) => {
|
|
55
|
+
const sub = subscribeToChainSpend({
|
|
56
|
+
lnd: args.lnd,
|
|
57
|
+
min_height: args.min_height,
|
|
58
|
+
output_script: args.output_script,
|
|
59
|
+
transaction_id: args.transaction_id,
|
|
60
|
+
transaction_vout: args.transaction_vout,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const finished = (err, res) => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
|
|
66
|
+
sub.removeAllListeners();
|
|
67
|
+
|
|
68
|
+
return cbk(err, res);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Stop looking when the spend is not found in time
|
|
72
|
+
const timer = setTimeout(() => {
|
|
73
|
+
return finished(null, {});
|
|
74
|
+
},
|
|
75
|
+
args.timeout || defaultTimeoutMs);
|
|
76
|
+
|
|
77
|
+
sub.once('confirmation', ({transaction}) => {
|
|
78
|
+
return finished(null, {transaction});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
sub.once('end', () => finished(null, {}));
|
|
82
|
+
|
|
83
|
+
sub.once('error', err => {
|
|
84
|
+
return finished([503, 'UnexpectedErrorGettingSpend', {err}]);
|
|
85
|
+
});
|
|
86
|
+
}],
|
|
87
|
+
},
|
|
88
|
+
returnResult({reject, resolve, of: 'getSpend'}, cbk));
|
|
89
|
+
});
|
|
90
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const {numberAsCompactInt} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
const byteCountOutpoint = 36;
|
|
4
|
+
const byteCountSequence = 4;
|
|
5
|
+
const compactIntSize = n => numberAsCompactInt({number: n}).encoded.length;
|
|
6
|
+
const hexByteLength = hex => hex.length / 2;
|
|
7
|
+
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
8
|
+
const witnessScaling = 4;
|
|
9
|
+
|
|
10
|
+
/** Calculate the weight contributed by a transaction input
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
script: <ScriptSig Script Hex String>
|
|
14
|
+
[witness]: [<Script Stack Element Hex String>]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@returns
|
|
18
|
+
<Input Weight Units Number>
|
|
19
|
+
*/
|
|
20
|
+
module.exports = ({script, witness}) => {
|
|
21
|
+
const scriptBytes = hexByteLength(script);
|
|
22
|
+
|
|
23
|
+
const base = [
|
|
24
|
+
byteCountOutpoint,
|
|
25
|
+
compactIntSize(scriptBytes),
|
|
26
|
+
scriptBytes,
|
|
27
|
+
byteCountSequence,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Non-witness bytes count four times as much as witness bytes
|
|
31
|
+
const baseWeight = sumOf(base) * witnessScaling;
|
|
32
|
+
|
|
33
|
+
// Exit early when there is no witness data for the input
|
|
34
|
+
if (!witness) {
|
|
35
|
+
return baseWeight;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const items = witness.map(n => {
|
|
39
|
+
return compactIntSize(hexByteLength(n)) + hexByteLength(n);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return baseWeight + compactIntSize(witness.length) + sumOf(items);
|
|
43
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Determine if a transaction input spends one of a set of contract outputs
|
|
2
|
+
|
|
3
|
+
Contract outputs without an output index refer to all outputs of the tx
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
id: <Input Spends Transaction Id Hex String>
|
|
7
|
+
outputs: [{
|
|
8
|
+
id: <Contract Transaction Id Hex String>
|
|
9
|
+
[vout]: <Contract Transaction Output Index Number>
|
|
10
|
+
}]
|
|
11
|
+
vout: <Input Spends Transaction Output Index Number>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@returns
|
|
15
|
+
<Input Spends A Contract Output Bool>
|
|
16
|
+
*/
|
|
17
|
+
module.exports = args => {
|
|
18
|
+
return !!args.outputs.find(output => {
|
|
19
|
+
// Exit early when the input does not spend the contract transaction
|
|
20
|
+
if (output.id !== args.id) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return output.vout === undefined || output.vout === args.vout;
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
/** Link HTLC payment resolutions that resolve over two stages
|
|
4
|
+
|
|
5
|
+
An HTLC on a local commitment is first spent into a second level transaction
|
|
6
|
+
and then the second level output is spent. LND keeps a first stage report as
|
|
7
|
+
well as the report of the second stage, so the second stage is linked to the
|
|
8
|
+
first stage to get the final outcome of the HTLC.
|
|
9
|
+
|
|
10
|
+
A second level transaction can spend multiple HTLCs. Each HTLC output in it
|
|
11
|
+
is at the same index as the HTLC input that funds it.
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
close_payments: [{
|
|
15
|
+
is_outgoing: <Payment Is Outgoing Bool>
|
|
16
|
+
is_paid: <Payment Is Claimed With Preimage Bool>
|
|
17
|
+
is_pending: <Payment Resolution Is Pending Bool>
|
|
18
|
+
is_refunded: <Payment Timed Out And Went Back To Payer Bool>
|
|
19
|
+
[spent_by]: <Spent By Transaction Id Hex String>
|
|
20
|
+
tokens: <Associated Tokens Number>
|
|
21
|
+
transaction_id: <Transaction Id Hex String>
|
|
22
|
+
transaction_vout: <Transaction Output Index Number>
|
|
23
|
+
}]
|
|
24
|
+
transactions: [{
|
|
25
|
+
id: <Transaction Id Hex String>
|
|
26
|
+
[transaction]: <Raw Transaction Hex String>
|
|
27
|
+
}]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@returns
|
|
31
|
+
{
|
|
32
|
+
payments: [{
|
|
33
|
+
is_outgoing: <Payment Is Outgoing Bool>
|
|
34
|
+
is_paid: <Payment Is Claimed With Preimage Bool>
|
|
35
|
+
is_pending: <Payment Resolution Is Pending Bool>
|
|
36
|
+
is_refunded: <Payment Timed Out And Went Back To Payer Bool>
|
|
37
|
+
[spent_by]: <Final Spent By Transaction Id Hex String>
|
|
38
|
+
tokens: <Associated Tokens Number>
|
|
39
|
+
transaction_id: <Transaction Id Hex String>
|
|
40
|
+
transaction_vout: <Transaction Output Index Number>
|
|
41
|
+
}]
|
|
42
|
+
}
|
|
43
|
+
*/
|
|
44
|
+
module.exports = args => {
|
|
45
|
+
// First stage reports are pending spends into a second level transaction
|
|
46
|
+
const firstStages = args.close_payments.filter(payment => {
|
|
47
|
+
return !!payment.is_pending && !!payment.spent_by;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Find the second stage report for each first stage report
|
|
51
|
+
const links = firstStages.map(firstStage => {
|
|
52
|
+
const isSameDirection = n => n.is_outgoing === firstStage.is_outgoing;
|
|
53
|
+
|
|
54
|
+
const secondStages = args.close_payments
|
|
55
|
+
.filter(n => n.transaction_id === firstStage.spent_by)
|
|
56
|
+
.filter(isSameDirection);
|
|
57
|
+
|
|
58
|
+
const otherFirstStages = firstStages
|
|
59
|
+
.filter(n => n !== firstStage)
|
|
60
|
+
.filter(n => n.spent_by === firstStage.spent_by)
|
|
61
|
+
.filter(isSameDirection);
|
|
62
|
+
|
|
63
|
+
// Exit early when no other HTLC like this is in the second level tx
|
|
64
|
+
if (!otherFirstStages.length) {
|
|
65
|
+
const [secondStage] = secondStages;
|
|
66
|
+
|
|
67
|
+
return {firstStage, secondStage};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [secondLevelTx] = args.transactions
|
|
71
|
+
.filter(n => !!n.transaction)
|
|
72
|
+
.filter(n => n.id === firstStage.spent_by)
|
|
73
|
+
.map(({transaction}) => componentsOfTransaction({transaction}));
|
|
74
|
+
|
|
75
|
+
// Exit early when the second level tx is not known to pair the outputs
|
|
76
|
+
if (!secondLevelTx) {
|
|
77
|
+
return {firstStage};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const vout = secondLevelTx.inputs.findIndex(input => {
|
|
81
|
+
if (input.id !== firstStage.transaction_id) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return input.vout === firstStage.transaction_vout;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const secondStage = secondStages.find(n => n.transaction_vout === vout);
|
|
89
|
+
|
|
90
|
+
return {firstStage, secondStage};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const linked = links.filter(link => !!link.secondStage);
|
|
94
|
+
|
|
95
|
+
// Second stages are folded into the first stage that they continue
|
|
96
|
+
const payments = args.close_payments
|
|
97
|
+
.filter(payment => !linked.find(n => n.secondStage === payment))
|
|
98
|
+
.map(payment => {
|
|
99
|
+
const link = linked.find(n => n.firstStage === payment);
|
|
100
|
+
|
|
101
|
+
// Exit early when there is no second stage to link
|
|
102
|
+
if (!link) {
|
|
103
|
+
return payment;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
is_outgoing: payment.is_outgoing,
|
|
108
|
+
is_paid: link.secondStage.is_paid,
|
|
109
|
+
is_pending: link.secondStage.is_pending,
|
|
110
|
+
is_refunded: link.secondStage.is_refunded,
|
|
111
|
+
spent_by: link.secondStage.spent_by,
|
|
112
|
+
tokens: payment.tokens,
|
|
113
|
+
transaction_id: payment.transaction_id,
|
|
114
|
+
transaction_vout: payment.transaction_vout,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return {payments};
|
|
119
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Describe the outcome of a closed channel HTLC payment output
|
|
2
|
+
|
|
3
|
+
{
|
|
4
|
+
[is_paid]: <Payment Is Claimed With Preimage Bool>
|
|
5
|
+
[is_pending]: <Payment Resolution Is Pending Bool>
|
|
6
|
+
[is_refunded]: <Payment Timed Out And Went Back To Payer Bool>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@returns
|
|
10
|
+
{
|
|
11
|
+
outcome: <Payment Outcome String>
|
|
12
|
+
}
|
|
13
|
+
*/
|
|
14
|
+
module.exports = args => {
|
|
15
|
+
if (!!args.is_paid) {
|
|
16
|
+
return {outcome: 'paid'};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!!args.is_refunded) {
|
|
20
|
+
return {outcome: 'refunded'};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!!args.is_pending) {
|
|
24
|
+
return {outcome: 'pending'};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {outcome: 'unclaimed'};
|
|
28
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
2
|
+
|
|
3
|
+
const inputWeight = require('./input_weight');
|
|
4
|
+
const isContractSpend = require('./is_contract_spend');
|
|
5
|
+
const transactionFee = require('./transaction_fee');
|
|
6
|
+
|
|
7
|
+
const {round} = Math;
|
|
8
|
+
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
9
|
+
|
|
10
|
+
/** Calculate the share of a resolving tx fee attributable to a channel
|
|
11
|
+
|
|
12
|
+
A resolving transaction may also spend outputs of other channels, in which
|
|
13
|
+
case the fee is pro-rated by the weight of the inputs that spend the
|
|
14
|
+
channel's own contract outputs. Inputs that spend neither are fee funding
|
|
15
|
+
inputs and are not counted against the channel's share.
|
|
16
|
+
|
|
17
|
+
Contract outputs without an output index refer to all outputs of the tx.
|
|
18
|
+
A second level transaction can be shared between channels so its outputs
|
|
19
|
+
are referred to individually.
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
contract_outputs: [{
|
|
23
|
+
id: <Channel Contract Transaction Id Hex String>
|
|
24
|
+
[vout]: <Channel Contract Transaction Output Index Number>
|
|
25
|
+
}]
|
|
26
|
+
id: <Resolving Transaction Id Hex String>
|
|
27
|
+
other_contract_outputs: [{
|
|
28
|
+
id: <Other Channel Contract Transaction Id Hex String>
|
|
29
|
+
[vout]: <Other Channel Contract Transaction Output Index Number>
|
|
30
|
+
}]
|
|
31
|
+
transactions: [{
|
|
32
|
+
[fee]: <Wallet Reported Fee Tokens Number>
|
|
33
|
+
id: <Transaction Id Hex String>
|
|
34
|
+
[transaction]: <Raw Transaction Hex String>
|
|
35
|
+
}]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@returns
|
|
39
|
+
{
|
|
40
|
+
[fee]: <Attributable Fee Tokens Number>
|
|
41
|
+
}
|
|
42
|
+
*/
|
|
43
|
+
module.exports = args => {
|
|
44
|
+
const tx = args.transactions.find(n => n.id === args.id);
|
|
45
|
+
|
|
46
|
+
// Exit early when the resolving transaction is not known
|
|
47
|
+
if (!tx || !tx.transaction) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const {inputs} = componentsOfTransaction({transaction: tx.transaction});
|
|
52
|
+
|
|
53
|
+
const own = inputs.filter(({id, vout}) => {
|
|
54
|
+
return isContractSpend({id, vout, outputs: args.contract_outputs});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const other = inputs.filter(({id, vout}) => {
|
|
58
|
+
return isContractSpend({id, vout, outputs: args.other_contract_outputs});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Exit early when the transaction does not spend the channel's outputs
|
|
62
|
+
if (!own.length) {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const {fee} = transactionFee({
|
|
67
|
+
fee: tx.fee,
|
|
68
|
+
transaction: tx.transaction,
|
|
69
|
+
transactions: args.transactions,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Exit early when the total fee of the transaction is not known
|
|
73
|
+
if (fee === undefined) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Exit early when the fee is not shared with other channels
|
|
78
|
+
if (!other.length) {
|
|
79
|
+
return {fee};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ownWeight = sumOf(own.map(inputWeight));
|
|
83
|
+
const otherWeight = sumOf(other.map(inputWeight));
|
|
84
|
+
|
|
85
|
+
return {fee: round(fee * ownWeight / (ownWeight + otherWeight))};
|
|
86
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const baseTxVbytes = 9;
|
|
2
|
+
const changeOutputVbytes = 43;
|
|
3
|
+
const {ceil} = Math;
|
|
4
|
+
const compactVbytes = 1;
|
|
5
|
+
const countVbytes = n => n <= maxCompactCount ? compactVbytes : wideVbytes;
|
|
6
|
+
const maxCompactCount = 252;
|
|
7
|
+
const splitOutputVbytes = 31;
|
|
8
|
+
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
9
|
+
const wideVbytes = 3;
|
|
10
|
+
|
|
11
|
+
/** Fee for a split transaction, rounding up so that the fee is not too low
|
|
12
|
+
|
|
13
|
+
Virtual sizes are upper bounds of the sizes the wallet estimates with
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
[has_change]: <Transaction Has a Change Output Bool>
|
|
17
|
+
inputs: <Inputs Count Number>
|
|
18
|
+
outputs: <Split Outputs Count Number>
|
|
19
|
+
rate: <Fee Tokens Per Virtual Byte Number>
|
|
20
|
+
vbytes: <Inputs Virtual Bytes Number>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@returns
|
|
24
|
+
<Fee Tokens Number>
|
|
25
|
+
*/
|
|
26
|
+
module.exports = args => {
|
|
27
|
+
const changeVbytes = !!args.has_change ? changeOutputVbytes : Number();
|
|
28
|
+
const outputsCount = args.outputs + Number(!!args.has_change);
|
|
29
|
+
|
|
30
|
+
const vbytes = sumOf([
|
|
31
|
+
baseTxVbytes,
|
|
32
|
+
changeVbytes,
|
|
33
|
+
countVbytes(args.inputs),
|
|
34
|
+
countVbytes(outputsCount),
|
|
35
|
+
args.outputs * splitOutputVbytes,
|
|
36
|
+
args.vbytes,
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
return ceil(vbytes * args.rate);
|
|
40
|
+
};
|