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.
@@ -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
+ };