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,59 @@
1
+ const {scriptAsScriptElements} = require('@alexbosworth/blockchain');
2
+
3
+ const bufferAsHex = buffer => buffer.toString('hex');
4
+ const fundingScriptElementsCount = 5;
5
+ const {isBuffer} = Buffer;
6
+ const OP_2 = 82;
7
+ const OP_CHECKMULTISIG = 174;
8
+ const publicKeyByteLength = 33;
9
+
10
+ /** Derive the funding public keys from the witness of the funding output spend
11
+
12
+ A close transaction spends the funding output with a 2 of 2 multisig witness
13
+ script that commits to both funding public keys.
14
+
15
+ {
16
+ [witness]: [<Witness Stack Element Hex String>]
17
+ }
18
+
19
+ @returns
20
+ {
21
+ [keys]: [<Funding Public Key Hex String>]
22
+ }
23
+ */
24
+ module.exports = ({witness}) => {
25
+ // Exit early when there is no witness script to look at
26
+ if (!witness || !witness.length) {
27
+ return {};
28
+ }
29
+
30
+ const [script] = [].concat(witness).reverse();
31
+
32
+ // Witness elements are always hex, a malformed script has no elements
33
+ const {elements} = scriptAsScriptElements({script});
34
+
35
+ // Exit early when the script is not shaped like a 2 of 2 multisig
36
+ if (!elements || elements.length !== fundingScriptElementsCount) {
37
+ return {};
38
+ }
39
+
40
+ const [op2, key1, key2, op2Again, opCheckMultisig] = elements;
41
+
42
+ // Exit early when the script is not a 2 of 2 multisig
43
+ if (op2 !== OP_2 || op2Again !== OP_2) {
44
+ return {};
45
+ }
46
+
47
+ if (opCheckMultisig !== OP_CHECKMULTISIG) {
48
+ return {};
49
+ }
50
+
51
+ const keys = [key1, key2];
52
+
53
+ // Exit early when the keys are not public keys
54
+ if (!!keys.find(n => !isBuffer(n) || n.length !== publicKeyByteLength)) {
55
+ return {};
56
+ }
57
+
58
+ return {keys: keys.map(bufferAsHex)};
59
+ };
@@ -0,0 +1,100 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {getBlock} = require('ln-service');
3
+ const {queryTransactions} = require('@alexbosworth/blockchain');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ const defaultTimeoutMs = 1000 * 10;
7
+ const {isArray} = Array;
8
+ const isHash = n => /^[0-9A-F]{64}$/i.test(n);
9
+
10
+ /** Get transactions in a block by their ids using the LND chain backend
11
+
12
+ When the block is not retrieved before the timeout, no txs are returned
13
+
14
+ {
15
+ height: <Block Height Number>
16
+ ids: [<Transaction Id Hex String>]
17
+ lnd: <Authenticated LND API Object>
18
+ [timeout]: <Stop Waiting For The Block After Milliseconds Number>
19
+ }
20
+
21
+ @returns via cbk or Promise
22
+ {
23
+ transactions: [{
24
+ id: <Transaction Id Hex String>
25
+ transaction: <Raw Transaction Hex String>
26
+ }]
27
+ }
28
+ */
29
+ module.exports = (args, cbk) => {
30
+ return new Promise((resolve, reject) => {
31
+ return asyncAuto({
32
+ // Check arguments
33
+ validate: cbk => {
34
+ if (!args.height) {
35
+ return cbk([400, 'ExpectedBlockHeightToGetBlockTransactions']);
36
+ }
37
+
38
+ if (!isArray(args.ids) || !args.ids.length) {
39
+ return cbk([400, 'ExpectedTransactionIdsToGetBlockTransactions']);
40
+ }
41
+
42
+ if (!!args.ids.find(n => !isHash(n))) {
43
+ return cbk([400, 'ExpectedTransactionIdHexStringsToGetBlockTxs']);
44
+ }
45
+
46
+ if (!args.lnd) {
47
+ return cbk([400, 'ExpectedLndToGetBlockTransactions']);
48
+ }
49
+
50
+ return cbk();
51
+ },
52
+
53
+ // Get the block from LND, giving up when it takes too long
54
+ getBlock: ['validate', ({}, cbk) => {
55
+ let isFinished = false;
56
+
57
+ const finished = (err, res) => {
58
+ // Exit early when the result was already returned
59
+ if (isFinished) {
60
+ return;
61
+ }
62
+
63
+ isFinished = true;
64
+
65
+ clearTimeout(timer);
66
+
67
+ return cbk(err, res);
68
+ };
69
+
70
+ // Stop waiting when the block is not retrieved in time
71
+ const timer = setTimeout(() => {
72
+ return finished(null, {});
73
+ },
74
+ args.timeout || defaultTimeoutMs);
75
+
76
+ return getBlock({height: args.height, lnd: args.lnd}, finished);
77
+ }],
78
+
79
+ // Find the transactions in the block
80
+ transactions: ['getBlock', ({getBlock}, cbk) => {
81
+ // Exit early when there is no block to look through
82
+ if (!getBlock.block) {
83
+ return cbk(null, {transactions: []});
84
+ }
85
+
86
+ try {
87
+ const {transactions} = queryTransactions({
88
+ block: getBlock.block,
89
+ ids: args.ids,
90
+ });
91
+
92
+ return cbk(null, {transactions});
93
+ } catch (err) {
94
+ return cbk([503, 'UnexpectedErrorParsingBlockTransactions', {err}]);
95
+ }
96
+ }],
97
+ },
98
+ returnResult({reject, resolve, of: 'transactions'}, cbk));
99
+ });
100
+ };
@@ -3,47 +3,54 @@ const asyncMapSeries = require('async/mapSeries');
3
3
  const {getChainTransactions} = require('ln-service');
4
4
  const {getClosedChannels} = require('ln-service');
5
5
  const {getHeight} = require('ln-service');
6
- const {getNetwork} = require('ln-sync');
7
- const {getNode} = require('ln-service');
8
6
  const {getNodeAlias} = require('ln-sync');
7
+ const {getPendingChannels} = require('ln-service');
9
8
  const {returnResult} = require('asyncjs-util');
10
9
 
10
+ const anchorOutput = require('./anchor_output');
11
+ const closeResolutions = require('./close_resolutions');
11
12
  const closingFees = require('./closing_fees');
12
- const getChannelResolution = require('./get_channel_resolution');
13
+ const contractOutputs = require('./contract_outputs');
14
+ const getCloseTransactions = require('./get_close_transactions');
13
15
 
14
16
  const defaultLimit = 20;
17
+ const flatten = arr => [].concat(...arr);
15
18
 
16
19
  /** Get the last channel close outcomes
17
20
 
18
21
  {
19
22
  [limit]: <Limit Number>
20
23
  lnd: <Authenticated LND API Object>
21
- request: <Request Function>
22
24
  }
23
25
 
24
26
  @returns via cbk or Promise
25
27
  {
26
- peer_public_key: <Peer Public Key Hex String>
27
- [peer_alias]: <Peer Alias Strring>
28
- [is_local_force_close]: <Channel Was Locally Force Closed Bool>
29
- [is_cooperative_close]: <Channel Was Cooperatively Closed Bool>
30
- [is_remote_force_close]: <Channel was Remotely Force Closed Bool>
31
- [peer_closed_channel]: <Peer Closed the Channel Bool>
32
- blocks_since_close: <Count of Blocks Since Close Number>
33
- capacity: <Channel Capacity Tokens Number>
34
- [channel_id]: <Channel Id String>
35
- channel_open: <Channel Funding Outpoint String>
36
- channel_close: <Channel Close Transaction Id Hex String>
37
- [channel_balance_spend]: <Channel Balance Spent In Tx Id Hex String>
38
- [channel_resolutions]: [{
39
- type: <Resolution Type String>
40
- value: <Value Number>
28
+ closes: [{
29
+ peer_public_key: <Peer Public Key Hex String>
30
+ [peer_alias]: <Peer Alias Strring>
31
+ [is_local_force_close]: <Channel Was Locally Force Closed Bool>
32
+ [is_cooperative_close]: <Channel Was Cooperatively Closed Bool>
33
+ [is_remote_force_close]: <Channel was Remotely Force Closed Bool>
34
+ [peer_opened_channel]: <Peer Opened the Channel Bool>
35
+ [peer_closed_channel]: <Peer Closed the Channel Bool>
36
+ blocks_since_close: <Count of Blocks Since Close Number>
37
+ capacity: <Channel Capacity Tokens Number>
38
+ [channel_id]: <Channel Id String>
39
+ channel_open: <Channel Funding Outpoint String>
40
+ channel_close: <Channel Close Transaction Id Hex String>
41
+ [channel_balance_spend]: <Channel Balance Spent In Tx Id Hex String>
42
+ [channel_resolutions]: [{
43
+ [transaction_id]: <Latest Resolving Transaction Id Hex String>
44
+ type: <Resolution Type String>
45
+ [value]: <Value Number>
46
+ }]
47
+ [is_breach_close]: <Channel Was Breach Closed Bool>
48
+ [closing_fee_paid]: <Closing Fees Paid Related To Channel Tokens Number>
49
+ [is_closing_fee_unknown]: <Closing Fees Paid Are Not Known Bool>
41
50
  }]
42
- [is_breach_close]: <Channel Was Breach Closed Bool>
43
- [closing_fee_paid]: <Closing Fees Paid Related To Channel Tokens Number>
44
51
  }
45
52
  */
46
- module.exports = ({limit, lnd, request}, cbk) => {
53
+ module.exports = ({limit, lnd}, cbk) => {
47
54
  return new Promise((resolve, reject) => {
48
55
  return asyncAuto({
49
56
  // Check arguments
@@ -52,10 +59,6 @@ module.exports = ({limit, lnd, request}, cbk) => {
52
59
  return cbk([400, 'ExpectedLndToGetChannelCloses']);
53
60
  }
54
61
 
55
- if (!request) {
56
- return cbk([400, 'ExpectedRequestFunctionToGetChannelCloses']);
57
- }
58
-
59
62
  return cbk();
60
63
  },
61
64
 
@@ -65,20 +68,14 @@ module.exports = ({limit, lnd, request}, cbk) => {
65
68
  // Get the current height
66
69
  getHeight: ['validate', ({}, cbk) => getHeight({lnd}, cbk)],
67
70
 
68
- // Get the network
69
- getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
71
+ // Get pending channels, their outputs may share resolving transactions
72
+ getPending: ['validate', ({}, cbk) => getPendingChannels({lnd}, cbk)],
70
73
 
71
74
  // Get on-chain transactions
72
75
  getTx: ['validate', ({}, cbk) => getChainTransactions({lnd}, cbk)],
73
76
 
74
- // Get spends
75
- getSpends: [
76
- 'getClosed',
77
- 'getHeight',
78
- 'getNetwork',
79
- 'getTx',
80
- async ({getClosed, getHeight, getNetwork, getTx}) =>
81
- {
77
+ // Select the most recent closed channels
78
+ getChannels: ['getClosed', ({getClosed}, cbk) => {
82
79
  const closedChannels = getClosed.channels
83
80
  .reverse()
84
81
  .filter(channel => !!channel.close_transaction_id)
@@ -86,30 +83,133 @@ module.exports = ({limit, lnd, request}, cbk) => {
86
83
 
87
84
  const channels = closedChannels.slice(Number(), limit || defaultLimit);
88
85
 
89
- return await asyncMapSeries(channels, async channel => {
90
- const {resolutions} = await getChannelResolution({
91
- request,
86
+ return cbk(null, channels);
87
+ }],
88
+
89
+ // Get the aliases of the closed channel peers
90
+ getAliases: ['getChannels', ({getChannels}, cbk) => {
91
+ return asyncMapSeries(getChannels, (channel, cbk) => {
92
+ return getNodeAlias({lnd, id: channel.partner_public_key}, cbk);
93
+ },
94
+ cbk);
95
+ }],
96
+
97
+ // Get close transactions that are not in the wallet from LND
98
+ getCloseTxs: ['getChannels', 'getTx', ({getChannels, getTx}, cbk) => {
99
+ return getCloseTransactions({
100
+ lnd,
101
+ channels: getChannels,
102
+ transactions: getTx.transactions,
103
+ },
104
+ cbk);
105
+ }],
106
+
107
+ // Channel closes
108
+ closes: [
109
+ 'getAliases',
110
+ 'getChannels',
111
+ 'getClosed',
112
+ 'getCloseTxs',
113
+ 'getHeight',
114
+ 'getPending',
115
+ 'getTx',
116
+ ({
117
+ getAliases,
118
+ getChannels,
119
+ getClosed,
120
+ getCloseTxs,
121
+ getHeight,
122
+ getPending,
123
+ getTx,
124
+ },
125
+ cbk) =>
126
+ {
127
+ // Close transactions found by LND are added to the wallet transactions
128
+ const transactions = []
129
+ .concat(getTx.transactions)
130
+ .concat(getCloseTxs.transactions);
131
+
132
+ // Close tx outputs and HTLC tx outputs belong to closed channels
133
+ const closedContractOutputs = getClosed.channels.map(channel => {
134
+ return contractOutputs({
92
135
  close_transaction_id: channel.close_transaction_id,
93
- is_cooperative_close: channel.is_cooperative_close,
94
- network: getNetwork.network,
95
- transactions: getTx.transactions.filter(n => !!n.transaction),
136
+ payments: channel.close_payments,
96
137
  });
138
+ });
97
139
 
98
- const {alias} = await getNodeAlias({
99
- lnd,
100
- id: channel.partner_public_key,
140
+ // Pending close tx outputs and HTLC outputs belong to closing channels
141
+ const pendingContractOutputs = getPending.pending_channels.map(n => {
142
+ return contractOutputs({
143
+ close_transaction_id: n.close_transaction_id,
144
+ payments: n.pending_payments || [],
101
145
  });
146
+ });
147
+
148
+ const allContractOutputs = flatten([]
149
+ .concat(closedContractOutputs)
150
+ .concat(pendingContractOutputs));
102
151
 
152
+ const closes = getChannels.map((channel, i) => {
153
+ const {alias} = getAliases[i];
103
154
  const currentHeight = getHeight.current_block_height;
104
- const isRemoteForceClose = channel.is_remote_force_close;
105
155
  const init = `${channel.transaction_id}:${channel.transaction_vout}`;
156
+ const isRemoteForceClose = channel.is_remote_force_close;
157
+
158
+ // Outputs of this channel are not shared with other channels
159
+ const ownContractOutputs = contractOutputs({
160
+ close_transaction_id: channel.close_transaction_id,
161
+ payments: channel.close_payments,
162
+ });
163
+
164
+ const otherContractOutputs = allContractOutputs.filter(output => {
165
+ return !ownContractOutputs.find(own => {
166
+ return own.id === output.id && own.vout === output.vout;
167
+ });
168
+ });
169
+
170
+ const closeTx = transactions
171
+ .filter(n => !!n.transaction)
172
+ .find(n => n.id === channel.close_transaction_id);
173
+
174
+ // Our anchor output is found in the close tx when it is not reported
175
+ const anchor = anchorOutput({
176
+ anchor_vout: channel.anchor_vout,
177
+ close_balance_spent_by: channel.close_balance_spent_by,
178
+ close_balance_vout: channel.close_balance_vout,
179
+ close_payments: channel.close_payments,
180
+ close_transaction_id: channel.close_transaction_id,
181
+ is_cooperative_close: channel.is_cooperative_close,
182
+ transaction: !!closeTx ? closeTx.transaction : undefined,
183
+ });
106
184
 
107
185
  const closingFeePaid = closingFees({
186
+ transactions,
187
+ anchor_is_confirmed: channel.anchor_is_confirmed,
188
+ anchor_is_pending: channel.anchor_is_pending,
189
+ anchor_spent_by: channel.anchor_spent_by,
190
+ anchor_vout: anchor.vout,
108
191
  capacity: channel.capacity,
109
192
  close_balance_spent_by: channel.close_balance_spent_by,
193
+ close_payments: channel.close_payments,
110
194
  close_transaction_id: channel.close_transaction_id,
195
+ is_cooperative_close: channel.is_cooperative_close,
196
+ is_partner_closed: channel.is_partner_closed,
111
197
  is_partner_initiated: channel.is_partner_initiated,
112
- transactions: getTx.transactions,
198
+ other_contract_outputs: otherContractOutputs,
199
+ });
200
+
201
+ const isFeeUnknown = closingFeePaid.fees === undefined;
202
+
203
+ const {resolutions} = closeResolutions({
204
+ transactions,
205
+ anchor_is_confirmed: channel.anchor_is_confirmed,
206
+ anchor_is_pending: channel.anchor_is_pending,
207
+ anchor_spent_by: channel.anchor_spent_by,
208
+ anchor_vout: anchor.vout,
209
+ close_balance_spent_by: channel.close_balance_spent_by,
210
+ close_balance_vout: channel.close_balance_vout,
211
+ close_payments: channel.close_payments,
212
+ close_transaction_id: channel.close_transaction_id,
113
213
  });
114
214
 
115
215
  return {
@@ -118,6 +218,7 @@ module.exports = ({limit, lnd, request}, cbk) => {
118
218
  is_local_force_close: channel.is_local_force_close || undefined,
119
219
  is_cooperative_close: channel.is_cooperative_close || undefined,
120
220
  is_remote_force_close: isRemoteForceClose || undefined,
221
+ peer_opened_channel: channel.is_partner_initiated || undefined,
121
222
  peer_closed_channel: channel.is_partner_closed || undefined,
122
223
  blocks_since_close: currentHeight - channel.close_confirm_height,
123
224
  capacity: channel.capacity,
@@ -125,16 +226,14 @@ module.exports = ({limit, lnd, request}, cbk) => {
125
226
  channel_open: init,
126
227
  channel_close: channel.close_transaction_id,
127
228
  channel_balance_spend: channel.close_balance_spent_by || undefined,
128
- channel_resolutions: resolutions || undefined,
229
+ channel_resolutions: !resolutions.length ? undefined : resolutions,
129
230
  is_breach_close: channel.is_breach_close || undefined,
130
231
  closing_fee_paid: closingFeePaid.fees || undefined,
232
+ is_closing_fee_unknown: isFeeUnknown || undefined,
131
233
  };
132
234
  });
133
- }],
134
235
 
135
- // Channel closes
136
- closes: ['getSpends', ({getSpends}, cbk) => {
137
- return cbk(null, {closes: getSpends.slice().reverse()});
236
+ return cbk(null, {closes: closes.reverse()});
138
237
  }],
139
238
  },
140
239
  returnResult({reject, resolve, of :'closes'}, cbk));
@@ -0,0 +1,125 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {componentsOfTransaction} = require('@alexbosworth/blockchain');
3
+ const {idForTransaction} = require('@alexbosworth/blockchain');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ const getSpendingTransaction = require('./get_spending_transaction');
7
+
8
+ const {isArray} = Array;
9
+
10
+ /** Get a close transaction by looking up the spend of its funding output
11
+
12
+ When the spend is not found or is not the close tx, nothing is returned
13
+
14
+ {
15
+ close_confirm_height: <Close Confirmed At Height Number>
16
+ close_transaction_id: <Close Transaction Id Hex String>
17
+ lnd: <Authenticated LND API Object>
18
+ transaction_id: <Funding Transaction Id Hex String>
19
+ transaction_vout: <Funding Transaction Output Index Number>
20
+ transactions: [{
21
+ id: <Transaction Id Hex String>
22
+ [transaction]: <Raw Transaction Hex String>
23
+ }]
24
+ }
25
+
26
+ @returns via cbk or Promise
27
+ {
28
+ [id]: <Close Transaction Id Hex String>
29
+ [transaction]: <Raw Close Transaction Hex String>
30
+ }
31
+ */
32
+ module.exports = (args, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ if (!args.close_confirm_height) {
38
+ return cbk([400, 'ExpectedCloseConfirmHeightToGetCloseFromSpend']);
39
+ }
40
+
41
+ if (!args.close_transaction_id) {
42
+ return cbk([400, 'ExpectedCloseTransactionIdToGetCloseFromSpend']);
43
+ }
44
+
45
+ if (!args.lnd) {
46
+ return cbk([400, 'ExpectedLndToGetCloseFromSpend']);
47
+ }
48
+
49
+ if (!args.transaction_id) {
50
+ return cbk([400, 'ExpectedFundingTransactionIdToGetCloseFromSpend']);
51
+ }
52
+
53
+ if (args.transaction_vout === undefined) {
54
+ return cbk([400, 'ExpectedFundingOutputIndexToGetCloseFromSpend']);
55
+ }
56
+
57
+ if (!isArray(args.transactions)) {
58
+ return cbk([400, 'ExpectedTransactionsToGetCloseFromSpend']);
59
+ }
60
+
61
+ return cbk();
62
+ },
63
+
64
+ // Find the funding output to look for the spend of
65
+ fundingOutput: ['validate', ({}, cbk) => {
66
+ const [fundingTx] = args.transactions
67
+ .filter(n => !!n.transaction)
68
+ .filter(n => n.id === args.transaction_id);
69
+
70
+ // Exit early when the funding transaction is not known
71
+ if (!fundingTx) {
72
+ return cbk();
73
+ }
74
+
75
+ const {outputs} = componentsOfTransaction({
76
+ transaction: fundingTx.transaction,
77
+ });
78
+
79
+ return cbk(null, outputs[args.transaction_vout]);
80
+ }],
81
+
82
+ // Get the transaction that spent the funding output
83
+ getSpend: ['fundingOutput', ({fundingOutput}, cbk) => {
84
+ // Exit early when the funding output script is not known
85
+ if (!fundingOutput) {
86
+ return cbk();
87
+ }
88
+
89
+ return getSpendingTransaction({
90
+ lnd: args.lnd,
91
+ min_height: args.close_confirm_height,
92
+ output_script: fundingOutput.script,
93
+ transaction_id: args.transaction_id,
94
+ transaction_vout: args.transaction_vout,
95
+ },
96
+ (err, res) => {
97
+ // Exit early when LND can't find the spend, leaving it unknown
98
+ if (!!err) {
99
+ return cbk();
100
+ }
101
+
102
+ return cbk(null, res.transaction);
103
+ });
104
+ }],
105
+
106
+ // The spend is the close transaction when it has the close tx id
107
+ close: ['getSpend', ({getSpend}, cbk) => {
108
+ // Exit early when the spend was not found
109
+ if (!getSpend) {
110
+ return cbk(null, {});
111
+ }
112
+
113
+ const {id} = idForTransaction({transaction: getSpend});
114
+
115
+ // Exit early when the spend is not the expected close tx
116
+ if (id !== args.close_transaction_id) {
117
+ return cbk(null, {});
118
+ }
119
+
120
+ return cbk(null, {id, transaction: getSpend});
121
+ }],
122
+ },
123
+ returnResult({reject, resolve, of: 'close'}, cbk));
124
+ });
125
+ };
@@ -0,0 +1,116 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMapSeries = require('async/mapSeries');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const getCloseFromSpend = require('./get_close_from_spend');
6
+ const getClosesInBlocks = require('./get_closes_in_blocks');
7
+
8
+ const {isArray} = Array;
9
+
10
+ /** Get close transactions that are missing from the wallet transactions
11
+
12
+ A close transaction spends the channel funding output. When the funding
13
+ transaction is in the wallet, LND can find the close transaction by looking
14
+ for the spend of the funding output.
15
+
16
+ When the spend is not found that way, the close transaction is looked for in
17
+ the block that it was confirmed in using the LND chain backend.
18
+
19
+ {
20
+ channels: [{
21
+ [close_confirm_height]: <Close Confirmed At Height Number>
22
+ close_transaction_id: <Close Transaction Id Hex String>
23
+ transaction_id: <Funding Transaction Id Hex String>
24
+ transaction_vout: <Funding Transaction Output Index Number>
25
+ }]
26
+ lnd: <Authenticated LND API Object>
27
+ transactions: [{
28
+ id: <Transaction Id Hex String>
29
+ [transaction]: <Raw Transaction Hex String>
30
+ }]
31
+ }
32
+
33
+ @returns via cbk or Promise
34
+ {
35
+ transactions: [{
36
+ id: <Close Transaction Id Hex String>
37
+ transaction: <Raw Close Transaction Hex String>
38
+ }]
39
+ }
40
+ */
41
+ module.exports = (args, cbk) => {
42
+ return new Promise((resolve, reject) => {
43
+ return asyncAuto({
44
+ // Check arguments
45
+ validate: cbk => {
46
+ if (!isArray(args.channels)) {
47
+ return cbk([400, 'ExpectedChannelsToGetCloseTransactions']);
48
+ }
49
+
50
+ if (!args.lnd) {
51
+ return cbk([400, 'ExpectedLndToGetCloseTransactions']);
52
+ }
53
+
54
+ if (!isArray(args.transactions)) {
55
+ return cbk([400, 'ExpectedTransactionsToGetCloseTransactions']);
56
+ }
57
+
58
+ return cbk();
59
+ },
60
+
61
+ // Channels with a close tx missing from the wallet and a height to look
62
+ missing: ['validate', ({}, cbk) => {
63
+ const knownIds = args.transactions
64
+ .filter(n => !!n.transaction)
65
+ .map(n => n.id);
66
+
67
+ const channels = args.channels
68
+ .filter(n => !!n.close_confirm_height)
69
+ .filter(n => !knownIds.includes(n.close_transaction_id));
70
+
71
+ return cbk(null, channels);
72
+ }],
73
+
74
+ // Look for the close transactions as spends of the funding outputs
75
+ getSpends: ['missing', ({missing}, cbk) => {
76
+ return asyncMapSeries(missing, (channel, cbk) => {
77
+ return getCloseFromSpend({
78
+ close_confirm_height: channel.close_confirm_height,
79
+ close_transaction_id: channel.close_transaction_id,
80
+ lnd: args.lnd,
81
+ transaction_id: channel.transaction_id,
82
+ transaction_vout: channel.transaction_vout,
83
+ transactions: args.transactions,
84
+ },
85
+ cbk);
86
+ },
87
+ cbk);
88
+ }],
89
+
90
+ // Find the remaining close transactions in their confirmation blocks
91
+ getBlockTxs: ['getSpends', 'missing', ({getSpends, missing}, cbk) => {
92
+ const found = getSpends.filter(n => !!n.id).map(n => n.id);
93
+
94
+ const remaining = missing.filter(channel => {
95
+ return !found.includes(channel.close_transaction_id);
96
+ });
97
+
98
+ return getClosesInBlocks({channels: remaining, lnd: args.lnd}, cbk);
99
+ }],
100
+
101
+ // Close transactions that were found
102
+ transactions: [
103
+ 'getBlockTxs',
104
+ 'getSpends',
105
+ ({getBlockTxs, getSpends}, cbk) =>
106
+ {
107
+ const transactions = []
108
+ .concat(getSpends.filter(n => !!n.transaction))
109
+ .concat(getBlockTxs.transactions);
110
+
111
+ return cbk(null, {transactions});
112
+ }],
113
+ },
114
+ returnResult({reject, resolve, of: 'transactions'}, cbk));
115
+ });
116
+ };