paid-services 3.2.0 → 3.5.1

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.
Files changed (75) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +13 -0
  3. package/capacity/accept_capacity_change.js +284 -0
  4. package/capacity/ask_for_change_details.js +538 -0
  5. package/capacity/ask_for_decrease.js +185 -0
  6. package/capacity/assemble_replacement_tx.js +171 -0
  7. package/capacity/capacity_change_requests.js +85 -0
  8. package/capacity/change_channel_capacity.js +189 -0
  9. package/capacity/encode_change_request.js +62 -0
  10. package/capacity/encode_sign_capacity_response.js +32 -0
  11. package/capacity/fee_for_replacement.js +108 -0
  12. package/capacity/finalize_capacity_replacement.js +67 -0
  13. package/capacity/get_capacity_change_requests.js +99 -0
  14. package/capacity/get_capacity_replacement.js +372 -0
  15. package/capacity/get_replacement_signature.js +232 -0
  16. package/capacity/index.js +4 -0
  17. package/capacity/initiate_capacity_change.js +507 -0
  18. package/capacity/interim_replacement_psbt.js +57 -0
  19. package/capacity/is_open_witness_script.js +40 -0
  20. package/capacity/parse_accept_request.js +58 -0
  21. package/capacity/parse_accept_response.js +52 -0
  22. package/capacity/parse_capacity_change_request.js +108 -0
  23. package/capacity/parse_sign_capacity_request.js +59 -0
  24. package/capacity/propose_capacity_change.js +364 -0
  25. package/capacity/sign_capacity_replacement.js +119 -0
  26. package/capacity/witness_script_from_close_txs.js +50 -0
  27. package/index.js +2 -0
  28. package/p2p/encode_peer_response.js +0 -1
  29. package/p2p/make_peer_request.js +3 -3
  30. package/p2p/service_peer_requests.js +10 -2
  31. package/package.json +7 -4
  32. package/service_types.json +16 -0
  33. package/test/capacity/test_assemble_replacement_tx.js +46 -0
  34. package/test/capacity/test_capacity_change_requests.js +55 -0
  35. package/test/capacity/test_encode_change_request.js +54 -0
  36. package/test/capacity/test_encode_sign_capacity_response.js +47 -0
  37. package/test/capacity/test_fee_for_replacement.js +40 -0
  38. package/test/capacity/test_finalize_capacity_replacement.js +45 -0
  39. package/test/capacity/test_interim_replacement_psbt.js +44 -0
  40. package/test/capacity/test_is_open_witness_script.js +40 -0
  41. package/test/capacity/test_parse_accept_request.js +42 -0
  42. package/test/capacity/test_parse_capacity_change_request.js +52 -0
  43. package/test/capacity/test_parse_sign_capacity_request.js +49 -0
  44. package/test/capacity/test_witness_script_from_close_txs.js +53 -0
  45. package/test/integration/test_accept_capacity_change.js +189 -0
  46. package/test/integration/test_change_channel_capacity.js +203 -0
  47. package/test/integration/test_decrease_channel_capacity.js +216 -0
  48. package/test/integration/test_increase_channel_capacity.js +201 -0
  49. package/test/integration/test_sign_capacity_replacement.js +142 -0
  50. package/test/p2p/test_encode_peer_request.js +53 -0
  51. package/test/p2p/test_encode_peer_response.js +38 -0
  52. package/test/p2p/test_encode_response_code.js +48 -0
  53. package/test/trades/test_decode_basic_trade.js +40 -0
  54. package/test/trades/test_decode_open_trade.js +50 -0
  55. package/test/trades/test_encode_open_trade.js +35 -0
  56. package/test/trades/test_encode_trade.js +37 -0
  57. package/trades/accept_trade.js +52 -0
  58. package/trades/create_trade.js +124 -44
  59. package/trades/decode_basic_trade.js +45 -0
  60. package/trades/decode_open_trade.js +69 -0
  61. package/trades/decode_trade.js +48 -66
  62. package/trades/decode_trade_secret.js +89 -0
  63. package/trades/encode_open_trade.js +89 -0
  64. package/trades/encode_trade.js +48 -33
  65. package/trades/encode_trade_secret.js +50 -0
  66. package/trades/finalize_trade_secret.js +116 -0
  67. package/trades/find_trade.js +314 -0
  68. package/trades/manage_trade.js +46 -35
  69. package/trades/network_from_network_record.js +8 -2
  70. package/trades/network_record_from_network.js +36 -0
  71. package/trades/network_record_from_request.js +6 -0
  72. package/trades/node_as_record.js +98 -0
  73. package/trades/record_as_node.js +113 -0
  74. package/trades/request_trade_by_id.js +89 -0
  75. package/trades/service_trade_requests.js +184 -0
@@ -0,0 +1,32 @@
1
+ const idType = '0';
2
+ const capacityChangeSignatureType = '1';
3
+
4
+ /** Encode the response to a sign capacity request
5
+
6
+ {
7
+ id: <Capacity Change Id Hex String>
8
+ signature: <Replacement Transaction Signature Hex String>
9
+ }
10
+
11
+ @returns
12
+ {
13
+ records: [{
14
+ type: <Record Type Number String>
15
+ value: <Record Value Hex Encoded String>
16
+ }]
17
+ }
18
+ */
19
+ module.exports = ({id, signature}) => {
20
+ return {
21
+ records: [
22
+ {
23
+ type: idType,
24
+ value: id,
25
+ },
26
+ {
27
+ type: capacityChangeSignatureType,
28
+ value: signature,
29
+ },
30
+ ],
31
+ };
32
+ };
@@ -0,0 +1,108 @@
1
+ const {Transaction} = require('bitcoinjs-lib');
2
+
3
+ const {ceil} = Math;
4
+ const decreaseBuffer = 44;
5
+ const dummyHash = Buffer.alloc(32);
6
+ const dummyIncreaseIndex = 1;
7
+ const dummyIndex = 0;
8
+ const dummyMultiSigScript = Buffer.alloc(71);
9
+ const dummyNullDummy = Buffer.alloc(1);
10
+ const dummyP2wsh = Buffer.alloc(68);
11
+ const dummyPublicKey = Buffer.alloc(33);
12
+ const dummySignature = Buffer.alloc(74);
13
+ const dummyTokens = 0;
14
+ const dummyVout = 0;
15
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
16
+ const increaseBuffer = 300;
17
+ const {max} = Math;
18
+ const weightAsVBytes = weight => weight / 4;
19
+
20
+ /** Calculate the chain fee required for the capacity replacement tx
21
+
22
+ {
23
+ capacity: <Original Channel Capacity Tokens Number>
24
+ commit_transaction_fee: <Commit Transaction Fee Tokens Number>
25
+ commit_transaction_weight: <Commit Transaction Weight Units Number>
26
+ decrease: [{
27
+ output: <Output Script Hex String>
28
+ tokens: <Spend Value to Chain Address Tokens Number>
29
+ }]
30
+ [increase]: <Add Funds Tokens Number>
31
+ tokens_per_vbyte: <Minimal Fee Rate Tokens Per VByte Number>
32
+ }
33
+
34
+ @returns
35
+ {
36
+ fee: <Estimated Replacement Chain Fee Tokens Number>
37
+ }
38
+ */
39
+ module.exports = args => {
40
+ // The cost to close is also known as the commit fee
41
+ const closeTransactionFee = args.commit_transaction_fee;
42
+
43
+ // The commitment transaction size will have to be paid for
44
+ const replaceSize = weightAsVBytes(args.commit_transaction_weight);
45
+
46
+ // Calculate the rate that the commitment tx is paying
47
+ const commitRate = closeTransactionFee / replaceSize;
48
+
49
+ // Create a dummy transaction which models the replacement
50
+ const tx = new Transaction();
51
+
52
+ // Add the decreases as outputs
53
+ args.decrease.forEach(({output, tokens}) => {
54
+ return tx.addOutput(hexAsBuffer(output), tokens);
55
+ });
56
+
57
+ // Add an output to represent the new channel output
58
+ tx.addOutput(dummyP2wsh, args.capacity);
59
+
60
+ args.decrease.forEach(({output}) => {
61
+ return tx.addOutput(hexAsBuffer(output), dummyTokens);
62
+ });
63
+
64
+ // Add an input to represent the old channel input
65
+ tx.addInput(dummyHash, dummyVout);
66
+
67
+ // Spending a channel close requires both signatures
68
+ const dummyWitnessStack = [
69
+ dummyNullDummy,
70
+ dummySignature,
71
+ dummySignature,
72
+ dummyMultiSigScript,
73
+ ];
74
+
75
+ // Add a witness to the input
76
+ tx.setWitness(dummyIndex, dummyWitnessStack);
77
+
78
+ // An increase means that there is an additional input
79
+ if (!!args.increase) {
80
+ tx.addInput(dummyHash, dummyVout);
81
+
82
+ // Add funds input spends from transit, which means signature + public key
83
+ tx.setWitness(dummyIncreaseIndex, [dummySignature, dummyPublicKey]);
84
+ }
85
+
86
+ // Add additional size for an additional input
87
+ const inputBuffer = !!args.increase ? increaseBuffer : dummyTokens;
88
+
89
+ // Add additional size for additional outputs
90
+ const outputBuffer = args.decrease.length * decreaseBuffer;
91
+
92
+ // Now that we have all inputs and outputs we can measure the virtual bytes
93
+ const size = tx.virtualSize() + inputBuffer + outputBuffer;
94
+
95
+ // The fee for the replacement tx must be above a minimum
96
+ const minFee = ceil(size * commitRate);
97
+
98
+ // The base fee for replacement is the fee of the replacement
99
+ const baseFee = args.commit_transaction_fee;
100
+
101
+ // There is an additional fee which is a relay fee to pay for the replaced tx
102
+ const deltaFee = ceil(replaceSize * args.tokens_per_vbyte);
103
+
104
+ // The new tx pays either min replacement fee or the min overall relay fee
105
+ const fee = max(minFee, closeTransactionFee + deltaFee);
106
+
107
+ return {fee};
108
+ };
@@ -0,0 +1,67 @@
1
+ const {script} = require('bitcoinjs-lib');
2
+ const {Transaction} = require('bitcoinjs-lib');
3
+
4
+ const bufferAsHex = buffer => buffer.toString('hex');
5
+ const {decompile} = script;
6
+ const encodeSig = (n, flag) => Buffer.concat([Buffer.from(n, 'hex'), flag]);
7
+ const {fromHex} = Transaction;
8
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
9
+ const {isBuffer} = Buffer;
10
+ const nullDummyStackItem = Buffer.alloc(0);
11
+ const signatureFlag = Buffer.from([Transaction.SIGHASH_ALL]);
12
+
13
+ /** Add signaturess to a capacity replacement transaction
14
+
15
+ {
16
+ [add_funds_vin]: <Add Funds Input Index>
17
+ [add_funds_public_key]: <Add Funds Transit Public Key Hex String>
18
+ [add_funds_signature]: <Add Funds Signature Hex String>
19
+ local_public_key: <Local MultiSig Public Key Hex String>
20
+ local_signature: <Local Replacement Signature Hex String>
21
+ funding_spend_vin: <Funding Transaction Output Spend Input Index Number>
22
+ remote_signature: <Peer Signature Hex String>
23
+ transaction: <Unsigned Replacement Transaction Hex String>
24
+ witness_script: <Funding Witness Script Hex Encoded String>
25
+ }
26
+
27
+ @returns
28
+ {
29
+ transaction: <Fully Signed Transaction Hex String>
30
+ }
31
+ */
32
+ module.exports = args => {
33
+ const funding = hexAsBuffer(args.witness_script);
34
+ const tx = fromHex(args.transaction);
35
+
36
+ const keys = decompile(funding).filter(isBuffer).map(bufferAsHex);
37
+
38
+ const signatures = [,];
39
+
40
+ // Find the indices to place the signatures
41
+ const localKeyIndex = keys.findIndex(n => n === args.local_public_key);
42
+ const remoteKeyIndex = keys.findIndex(n => n !== args.local_public_key);
43
+
44
+ // Add the local signature to the funding witness stack
45
+ signatures[localKeyIndex] = encodeSig(args.local_signature, signatureFlag);
46
+
47
+ // Add the remote signature to the funding witness stack
48
+ signatures[remoteKeyIndex] = encodeSig(args.remote_signature, signatureFlag);
49
+
50
+ const witness = []
51
+ .concat([nullDummyStackItem])
52
+ .concat(signatures)
53
+ .concat([funding]);
54
+
55
+ // Fill in the witness stack including the close spend
56
+ tx.setWitness(args.funding_spend_vin, witness);
57
+
58
+ // The increase in funds also needs a signature added
59
+ if (!!args.add_funds_signature) {
60
+ tx.setWitness(args.add_funds_vin, [
61
+ encodeSig(args.add_funds_signature, signatureFlag),
62
+ hexAsBuffer(args.add_funds_public_key),
63
+ ]);
64
+ }
65
+
66
+ return {transaction: tx.toHex()};
67
+ };
@@ -0,0 +1,99 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {getChannels} = require('ln-service');
3
+ const {returnResult} = require('asyncjs-util');
4
+ const {subscribeToPeerMessages} = require('ln-service');
5
+
6
+ const capacityChangeRequests = require('./capacity_change_requests');
7
+ const {serviceTypeChangeCapacity} = require('./../service_types');
8
+ const parseCapacityChangeRequest = require('./parse_capacity_change_request');
9
+ const {servicePeerRequests} = require('./../p2p');
10
+
11
+ const defaultDelayMs = 1000 * 10;
12
+ const waitForRequestsTimeoutMs = 5000;
13
+
14
+ /** Get capacity change requests
15
+
16
+ {
17
+ [delay]: <Milliseconds To Wait For Change Requests Number>
18
+ lnd: <Authenticated LND API Object>
19
+ }
20
+
21
+ @returns via cbk or Promise
22
+ {
23
+ requests: [{
24
+ [address]: <Cooperative Close Address String>
25
+ capacity: <Channel Capacity Tokens Number>
26
+ channel: <Standard Format Channel Id String>
27
+ [decrease]: <Reduce Capacity Tokens Number>
28
+ from: <From Node Public Key Id Hex String>
29
+ id: <Change Request Id Hex String>
30
+ [increase]: <Add Capacity Tokens Number>
31
+ [type]: <Intended Replacement Channel Channel Type Flags Number>
32
+ }]
33
+ }
34
+ */
35
+ module.exports = ({delay, lnd}, cbk) => {
36
+ return new Promise((resolve, reject) => {
37
+ return asyncAuto({
38
+ // Check arguments
39
+ validate: cbk => {
40
+ if (!lnd) {
41
+ return cbk([400, 'ExpectedLndToGetCapacityChangeRequests']);
42
+ }
43
+
44
+ return cbk();
45
+ },
46
+
47
+ // Wait for incoming capacity change requests
48
+ getRequests: ['validate', ({}, cbk) => {
49
+ const requests = [];
50
+ const service = servicePeerRequests({lnd});
51
+
52
+ // Listen for capacity change requests
53
+ service.request({type: serviceTypeChangeCapacity}, (req, res) => {
54
+ const {from, records} = req;
55
+
56
+ const {request} = parseCapacityChangeRequest({from, records});
57
+
58
+ // Exit early when the change request message was not understood
59
+ if (!request) {
60
+ return res.failure([400, 'FailedToParseChangeRequest']);
61
+ }
62
+
63
+ // Only add new requests
64
+ if (!requests.find(n => n.id === request.id)) {
65
+ requests.push(request);
66
+ }
67
+
68
+ return res.success({});
69
+ });
70
+
71
+ return setTimeout(() => {
72
+ // Stop listening for requests after a little while
73
+ service.stop({});
74
+
75
+ return cbk(null, requests);
76
+ },
77
+ delay || waitForRequestsTimeoutMs);
78
+ }],
79
+
80
+ // Get the set of channels
81
+ getChannels: ['getRequests', ({}, cbk) => getChannels({lnd}, cbk)],
82
+
83
+ // Final set of capacity change requests
84
+ requests: [
85
+ 'getChannels',
86
+ 'getRequests',
87
+ ({getChannels, getRequests}, cbk) =>
88
+ {
89
+ const {requests} = capacityChangeRequests({
90
+ channels: getChannels.channels,
91
+ requests: getRequests,
92
+ });
93
+
94
+ return cbk(null, {requests});
95
+ }],
96
+ },
97
+ returnResult({reject, resolve, of: 'requests'}, cbk));
98
+ });
99
+ };
@@ -0,0 +1,372 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {closeChannel} = require('ln-service');
3
+ const {decodeChanId} = require('bolt07');
4
+ const {getChainFeeRate} = require('ln-service');
5
+ const {getChainTransactions} = require('ln-service');
6
+ const {getHeight} = require('ln-service');
7
+ const {getPendingChannels} = require('ln-service');
8
+ const {openChannels} = require('ln-service');
9
+ const {returnResult} = require('asyncjs-util');
10
+ const {Transaction} = require('bitcoinjs-lib');
11
+
12
+ const assembleReplacementTx = require('./assemble_replacement_tx');
13
+ const feeForReplacement = require('./fee_for_replacement');
14
+ const signCapacityReplacement = require('./sign_capacity_replacement');
15
+
16
+ const blocksBuffer = 2e3;
17
+ const bufferAsHex = buffer => buffer.toString('hex');
18
+ const defaultIncrease = 0;
19
+ const {fromHex} = Transaction;
20
+ const fuzzBlocks = 10;
21
+ const highValue = 3000;
22
+ const {isArray} = Array;
23
+ const maxCommitmentOutputs = 4;
24
+ const maxHighValueOutputs = 2;
25
+ const notFoundIndex = -1;
26
+ const positive = n => Math.max(1, n);
27
+ const slowTarget = 1000;
28
+ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
29
+ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
30
+
31
+ /** Close a channel and get a capacity replacement transaction
32
+
33
+ {
34
+ [add_funds_transaction_id]: <Adding Funds Input Tx Id Hex Encoded String>
35
+ [add_funds_transaction_vout]: <Adding Funds Output Index Number>
36
+ bitcoinjs_network: <BitcoinJs Network Name String>
37
+ decrease: [{
38
+ address: <Spend to Chain Address String>
39
+ output: <Spend to Output Script Hex String>
40
+ tokens: <Spend Value Number>
41
+ }]
42
+ id: <Original Channel Standard Format Channel Id String>
43
+ [increase]: <Increase Channel Size Tokens Number>
44
+ [is_private]: <Original Channel Is Private Bool>
45
+ lnd: <Authenticated LND API Object>
46
+ transaction_id: <Original Funding Transaction Id Hex String>
47
+ transaction_vout: <Original Funding Output Index Number>
48
+ }
49
+
50
+ @returns via cbk or Promise
51
+ {
52
+ [add_funds_vin]: <Funding Spend Input Index Number>
53
+ capacity: <Replacement Channel Capacity Tokens Number>
54
+ remote_balance: <Initial Balance for Peer Tokens Number>
55
+ signature: <Signature for Replacement Transaction Hex Encoded String>
56
+ signing_key: <Signing Public Key Hex String>
57
+ pending_channel_id: <Proposed Channel Pending Id Hex String>
58
+ transaction_id: <Replacement Channel Transaction Id Hex String>
59
+ transaction_vin: <Original Channel Input Index Number>
60
+ transaction_vout: <Replacement Channel Transaction Output Index Number>
61
+ unsigned_transaction: <Unsigned Transaction Hex Encoded String>
62
+ witness_script: <Original Channel Witness Script Hex String>
63
+ }
64
+ */
65
+ module.exports = (args, cbk) => {
66
+ return new Promise((resolve, reject) => {
67
+ return asyncAuto({
68
+ // Check arguments
69
+ validate: cbk => {
70
+ if (!args.bitcoinjs_network) {
71
+ return cbk([400, 'ExpectedNetworkNameToGetCapacityReplacement']);
72
+ }
73
+
74
+ if (!isArray(args.decrease)) {
75
+ return cbk([400, 'ExpectedDecreaseArrayToGetCapacityReplacement']);
76
+ }
77
+
78
+ if (!args.lnd) {
79
+ return cbk([400, 'ExpectedLndApiToGetCapacityReplacement']);
80
+ }
81
+
82
+ if (!args.transaction_id) {
83
+ return cbk([400, 'ExpectedFundingTxIdToGetCapacityReplacement']);
84
+ }
85
+
86
+ if (args.transaction_vout === undefined) {
87
+ return cbk([400, 'ExpectedFundingOutIndexToGetCapacityReplacement']);
88
+ }
89
+
90
+ return cbk();
91
+ },
92
+
93
+ // Get the chain fee rate for a slow confirmation to calculate fee delta
94
+ getFeeRate: ['validate', ({}, cbk) => {
95
+ return getChainFeeRate({
96
+ confirmation_target: slowTarget,
97
+ lnd: args.lnd,
98
+ },
99
+ cbk);
100
+ }],
101
+
102
+ // Get the current chain height
103
+ getHeight: ['validate', ({}, cbk) => getHeight({lnd: args.lnd}, cbk)],
104
+
105
+ // Force close the channel in order to get a non-final sequence number
106
+ channelClose: ['getFeeRate', ({}, cbk) => {
107
+ return closeChannel({
108
+ lnd: args.lnd,
109
+ is_force_close: true,
110
+ transaction_id: args.transaction_id,
111
+ transaction_vout: args.transaction_vout,
112
+ },
113
+ cbk);
114
+ }],
115
+
116
+ // Get the pending channels
117
+ getPending: ['channelClose', ({}, cbk) => {
118
+ return getPendingChannels({lnd: args.lnd}, cbk);
119
+ }],
120
+
121
+ // Find the pending closing channel
122
+ pendingChannel: ['getPending', ({getPending}, cbk) => {
123
+ const channel = getPending.pending_channels.find(chan => {
124
+ if (chan.transaction_id !== args.transaction_id) {
125
+ return false;
126
+ }
127
+
128
+ return chan.transaction_vout === args.transaction_vout;
129
+ });
130
+
131
+ if (!channel) {
132
+ return cbk([503, 'FailedToFindPendingCloseChannelForReplacement']);
133
+ }
134
+
135
+ if (!!channel.pending_payments && !!channel.pending_payments.length) {
136
+ return cbk([503, 'ExpectedNoActiveHtlcsOnCapacityChangeChannel']);
137
+ }
138
+
139
+ return cbk(null, {
140
+ capacity: channel.capacity,
141
+ partner_public_key: channel.partner_public_key,
142
+ remote_balance: channel.remote_balance,
143
+ });
144
+ }],
145
+
146
+ // Get the chain transactions to pull out the raw close tx
147
+ getUpdatedTx: ['channelClose', 'getHeight', ({getHeight}, cbk) => {
148
+ return getChainTransactions({
149
+ after: getHeight.current_block_height - fuzzBlocks,
150
+ lnd: args.lnd,
151
+ },
152
+ cbk);
153
+ }],
154
+
155
+ // Find the force close transaction
156
+ closeTx: [
157
+ 'channelClose',
158
+ 'getUpdatedTx',
159
+ 'pendingChannel',
160
+ ({channelClose, getUpdatedTx, pendingChannel}, cbk) =>
161
+ {
162
+ const closeId = channelClose.transaction_id;
163
+ const id = args.transaction_id;
164
+ const {capacity} = pendingChannel;
165
+
166
+ const tx = getUpdatedTx.transactions.find(n => n.id === closeId);
167
+
168
+ if (!tx || !tx.transaction) {
169
+ return cbk([503, 'ExpectedCloseTransactionInTransactionsList']);
170
+ }
171
+
172
+ const commitment = fromHex(tx.transaction);
173
+
174
+ // Make sure there aren't unexpectedly too-many outputs
175
+ if (commitment.outs.length > maxCommitmentOutputs) {
176
+ return cbk([503, 'ExpectedFewerOutputsOnCommitmentTransaction']);
177
+ }
178
+
179
+ const highValueOuts = commitment.outs.filter(n => n.value > highValue);
180
+
181
+ // There shouldn't be many high value outputs
182
+ if (highValueOuts.length > maxHighValueOutputs) {
183
+ return cbk([500, 'ExpectedFewerHighValueOutputsOnCommitTx']);
184
+ }
185
+
186
+ // Input details for the commitment transaction
187
+ const [{hash, index, witness}, other] = commitment.ins;
188
+
189
+ // The commitment transaction should be spending the funding outpoint
190
+ if (!hash.equals(txIdAsHash(id)) || index !== args.transaction_vout) {
191
+ return cbk([503, 'ExpectedSpendingOpenTransactionOutputIndex']);
192
+ }
193
+
194
+ // There should be no other inputs on the closing tx
195
+ if (!!other) {
196
+ return cbk([503, 'ExpectedSingleInputForCloseTransaction']);
197
+ }
198
+
199
+ // The spending script is the final element in the witness
200
+ const [fundingScript] = witness.slice().reverse();
201
+
202
+ // The difference between commitment outputs and its input is the fee
203
+ const fee = capacity - sumOf(commitment.outs.map(n => n.value));
204
+
205
+ return cbk(null, {
206
+ fee,
207
+ id: commitment.getId(),
208
+ script: bufferAsHex(fundingScript),
209
+ transaction: tx.transaction,
210
+ weight: commitment.weight(),
211
+ });
212
+ }],
213
+
214
+ // Find the open transaction
215
+ openTx: ['getUpdatedTx', ({getUpdatedTx}, cbk) => {
216
+ const funding = fromHex(args.open_transaction);
217
+ const id = args.transaction_id;
218
+
219
+ if (!funding.outs[args.transaction_vout]) {
220
+ return cbk([503, 'ExpectedTransactionOutputOnFundingTransaction']);
221
+ }
222
+
223
+ const capacity = funding.outs[args.transaction_vout].value;
224
+ const output = bufferAsHex(funding.outs[args.transaction_vout].script);
225
+
226
+ return cbk(null, {capacity, output});
227
+ }],
228
+
229
+ // Calculate the new channel's capacity
230
+ newCapacity: [
231
+ 'closeTx',
232
+ 'getFeeRate',
233
+ 'pendingChannel',
234
+ ({closeTx, getFeeRate, pendingChannel}, cbk) =>
235
+ {
236
+ // The fee for the replacement tx must meet the relay rate minimum
237
+ const {fee} = feeForReplacement({
238
+ capacity: pendingChannel.capacity,
239
+ commit_transaction_fee: closeTx.fee,
240
+ commit_transaction_weight: closeTx.weight,
241
+ decrease: args.decrease,
242
+ increase: args.increase,
243
+ tokens_per_vbyte: getFeeRate.tokens_per_vbyte,
244
+ });
245
+
246
+ // The new capacity is capacity + increase - decreases - tx fee
247
+ const newCapacity = sumOf([
248
+ pendingChannel.capacity,
249
+ args.increase || defaultIncrease,
250
+ -sumOf(args.decrease.map(n => n.tokens)),
251
+ -fee,
252
+ ]);
253
+
254
+ return cbk(null, newCapacity);
255
+ }],
256
+
257
+ // Propose the new channel to replace the existing one
258
+ proposeChannel: [
259
+ 'newCapacity',
260
+ 'pendingChannel',
261
+ ({newCapacity, pendingChannel}, cbk) =>
262
+ {
263
+ return openChannels({
264
+ channels: [{
265
+ capacity: newCapacity,
266
+ give_tokens: pendingChannel.remote_balance,
267
+ is_private: args.is_private,
268
+ partner_public_key: pendingChannel.partner_public_key,
269
+ is_private: args.is_private,
270
+ }],
271
+ is_avoiding_broadcast: true,
272
+ lnd: args.lnd,
273
+ },
274
+ cbk);
275
+ }],
276
+
277
+ // Put together the unsigned replacement transaction
278
+ replacementTx: [
279
+ 'closeTx',
280
+ 'newCapacity',
281
+ 'proposeChannel',
282
+ ({closeTx, newCapacity, proposeChannel}, cbk) =>
283
+ {
284
+ // The replacement tx pays to the proposed channel multisig address
285
+ const [{address}] = proposeChannel.pending;
286
+
287
+ try {
288
+ const replacement = assembleReplacementTx({
289
+ add_funds_transaction_id: args.add_funds_transaction_id,
290
+ add_funds_transaction_vout: args.add_funds_transaction_vout,
291
+ bitcoinjs_network: args.bitcoinjs_network,
292
+ close_transaction: closeTx.transaction,
293
+ decrease: args.decrease.map(decrease => ({
294
+ output: decrease.output,
295
+ tokens: decrease.tokens,
296
+ })),
297
+ funding_address: address,
298
+ funding_tokens: newCapacity,
299
+ transaction_id: args.transaction_id,
300
+ transaction_vout: args.transaction_vout,
301
+ });
302
+
303
+ return cbk(null, {
304
+ add_funds_vin: replacement.add_funds_vin,
305
+ transaction: replacement.transaction,
306
+ transaction_vin: replacement.transaction_vin,
307
+ transaction_id: replacement.transaction_id,
308
+ transaction_vout: replacement.transaction_vout,
309
+ });
310
+ } catch (err) {
311
+ return cbk([503, err.message]);
312
+ }
313
+ }],
314
+
315
+ // Partially sign the replacement transaction using the 2:2 key
316
+ signReplacement: [
317
+ 'closeTx',
318
+ 'openTx',
319
+ 'pendingChannel',
320
+ 'replacementTx',
321
+ ({closeTx, openTx, pendingChannel, replacementTx}, cbk) =>
322
+ {
323
+ return signCapacityReplacement({
324
+ capacity: openTx.capacity,
325
+ lnd: args.lnd,
326
+ output: openTx.output,
327
+ script: closeTx.script,
328
+ transaction: replacementTx.transaction,
329
+ vin: replacementTx.transaction_vin,
330
+ },
331
+ cbk);
332
+ }],
333
+
334
+ // Details of proposed replacement
335
+ proposedReplacement: [
336
+ 'closeTx',
337
+ 'newCapacity',
338
+ 'pendingChannel',
339
+ 'proposeChannel',
340
+ 'replacementTx',
341
+ 'signReplacement',
342
+ ({
343
+ closeTx,
344
+ newCapacity,
345
+ pendingChannel,
346
+ proposeChannel,
347
+ replacementTx,
348
+ signReplacement,
349
+ },
350
+ cbk) =>
351
+ {
352
+ const [pending] = proposeChannel.pending;
353
+
354
+ return cbk(null, {
355
+ add_funds_vin: replacementTx.add_funds_vin,
356
+ capacity: newCapacity,
357
+ force_close_tx_id: closeTx.id,
358
+ remote_balance: pendingChannel.remote_balance,
359
+ signature: signReplacement.signature,
360
+ signing_key: signReplacement.key,
361
+ pending_channel_id: pending.id,
362
+ transaction_id: replacementTx.transaction_id,
363
+ transaction_vin: replacementTx.transaction_vin,
364
+ transaction_vout: replacementTx.transaction_vout,
365
+ unsigned_transaction: replacementTx.transaction,
366
+ witness_script: closeTx.script,
367
+ });
368
+ }],
369
+ },
370
+ returnResult({reject, resolve, of: 'proposedReplacement'}, cbk));
371
+ });
372
+ };