paid-services 3.1.2 → 3.5.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.
Files changed (83) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +18 -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 +6 -0
  28. package/p2p/encode_peer_request.js +59 -0
  29. package/p2p/encode_peer_response.js +44 -0
  30. package/p2p/encode_response_code.js +49 -0
  31. package/p2p/index.js +4 -0
  32. package/p2p/make_peer_request.js +151 -0
  33. package/p2p/parse_request_message.js +103 -0
  34. package/p2p/parse_response_code.js +60 -0
  35. package/p2p/return_failure_response.js +43 -0
  36. package/p2p/return_success_response.js +47 -0
  37. package/p2p/service_peer_requests.js +108 -0
  38. package/package.json +8 -4
  39. package/service_types.json +16 -0
  40. package/test/capacity/test_assemble_replacement_tx.js +46 -0
  41. package/test/capacity/test_capacity_change_requests.js +55 -0
  42. package/test/capacity/test_encode_change_request.js +54 -0
  43. package/test/capacity/test_encode_sign_capacity_response.js +47 -0
  44. package/test/capacity/test_fee_for_replacement.js +40 -0
  45. package/test/capacity/test_finalize_capacity_replacement.js +45 -0
  46. package/test/capacity/test_interim_replacement_psbt.js +44 -0
  47. package/test/capacity/test_is_open_witness_script.js +40 -0
  48. package/test/capacity/test_parse_accept_request.js +42 -0
  49. package/test/capacity/test_parse_capacity_change_request.js +52 -0
  50. package/test/capacity/test_parse_sign_capacity_request.js +49 -0
  51. package/test/capacity/test_witness_script_from_close_txs.js +53 -0
  52. package/test/integration/test_accept_capacity_change.js +189 -0
  53. package/test/integration/test_change_channel_capacity.js +203 -0
  54. package/test/integration/test_decrease_channel_capacity.js +216 -0
  55. package/test/integration/test_increase_channel_capacity.js +201 -0
  56. package/test/integration/test_service_peer_requests.js +59 -0
  57. package/test/integration/test_sign_capacity_replacement.js +142 -0
  58. package/test/p2p/test_encode_peer_request.js +53 -0
  59. package/test/p2p/test_encode_peer_response.js +38 -0
  60. package/test/p2p/test_encode_response_code.js +48 -0
  61. package/test/trades/test_decode_basic_trade.js +40 -0
  62. package/test/trades/test_decode_open_trade.js +50 -0
  63. package/test/trades/test_encode_open_trade.js +35 -0
  64. package/test/trades/test_encode_trade.js +37 -0
  65. package/trades/accept_trade.js +52 -0
  66. package/trades/create_trade.js +124 -44
  67. package/trades/decode_basic_trade.js +45 -0
  68. package/trades/decode_open_trade.js +69 -0
  69. package/trades/decode_trade.js +48 -66
  70. package/trades/decode_trade_secret.js +89 -0
  71. package/trades/encode_open_trade.js +89 -0
  72. package/trades/encode_trade.js +48 -33
  73. package/trades/encode_trade_secret.js +50 -0
  74. package/trades/finalize_trade_secret.js +116 -0
  75. package/trades/find_trade.js +314 -0
  76. package/trades/manage_trade.js +46 -35
  77. package/trades/network_from_network_record.js +8 -2
  78. package/trades/network_record_from_network.js +36 -0
  79. package/trades/network_record_from_request.js +6 -0
  80. package/trades/node_as_record.js +98 -0
  81. package/trades/record_as_node.js +113 -0
  82. package/trades/request_trade_by_id.js +89 -0
  83. package/trades/service_trade_requests.js +184 -0
@@ -0,0 +1,185 @@
1
+ const {address} = require('bitcoinjs-lib');
2
+ const asyncAuto = require('async/auto');
3
+ const {createChainAddress} = require('ln-service');
4
+ const {getNetwork} = require('ln-sync');
5
+ const {networks} = require('bitcoinjs-lib');
6
+ const {returnResult} = require('asyncjs-util');
7
+
8
+ const bufferAsHex = buffer => buffer.toString('hex');
9
+ const dust = 550;
10
+ const isNumber = n => !isNaN(n);
11
+ const {toOutputScript} = address;
12
+
13
+ /** Ask for details about a decrease
14
+
15
+ {
16
+ ask: <Ask Function>
17
+ lnd: <Authenticated LND API Object>
18
+ max: <Maximum Possible Decrease Number>
19
+ }
20
+
21
+ @returns via cbk or Promise
22
+ {
23
+ [address]: <Send Funds to Address String>
24
+ is_final: <Decrease is Final Decrease Bool>
25
+ [output]: <Output Script Hex String>
26
+ tokens: <Withdraw Tokens Number>
27
+ }
28
+ */
29
+ module.exports = ({ask, lnd, max}, cbk) => {
30
+ return new Promise((resolve, reject) => {
31
+ return asyncAuto({
32
+ // Check arguments
33
+ validate: cbk => {
34
+ if (!ask) {
35
+ return cbk([400, 'ExpectedAskFunctionToAskForDecrease']);
36
+ }
37
+
38
+ if (!lnd) {
39
+ return cbk([400, 'ExpectedAuthenticatedLndToAskForDecrease']);
40
+ }
41
+
42
+ if (!max) {
43
+ return cbk([400, 'ExpectedMaximumAvaialbleTok'])
44
+ }
45
+
46
+ return cbk();
47
+ },
48
+
49
+ // Ask for the amount to decrease
50
+ askForAmount: ['validate', ({}, cbk) => {
51
+ return ask({
52
+ default: '0',
53
+ name: 'amount',
54
+ message: `How much do you want to spend out (max: ${max})?`,
55
+ type: 'input',
56
+ validate: input => {
57
+ if (!isNumber(input) || !Number.isInteger(Number(input))) {
58
+ return false;
59
+ }
60
+
61
+ if (!!Number(input) && Number(input) < dust) {
62
+ return false;
63
+ }
64
+
65
+ if (!!Number(input) && Number(input) > max) {
66
+ return `The maximum possible to decrease is ${max}`;
67
+ }
68
+
69
+ return true;
70
+ },
71
+ },
72
+ ({amount}) => cbk(null, amount));
73
+ }],
74
+
75
+ // Get network name
76
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
77
+
78
+ // Ask if withdrawing to an external address
79
+ askForExternal: ['askForAmount', ({askForAmount}, cbk) => {
80
+ if (!Number(askForAmount)) {
81
+ return cbk();
82
+ }
83
+
84
+ return ask({
85
+ default: false,
86
+ name: 'spend',
87
+ message: `Spend ${askForAmount} to an external address?`,
88
+ type: 'confirm',
89
+ },
90
+ ({spend}) => cbk(null, spend));
91
+ }],
92
+
93
+ // Ask for an external address
94
+ askForAddress: [
95
+ 'askForAmount',
96
+ 'askForExternal',
97
+ ({askForAmount, askForExternal}, cbk) =>
98
+ {
99
+ // Exit early when the withdraw address is internal
100
+ if (askForExternal !== true) {
101
+ return cbk();
102
+ }
103
+
104
+ return ask({
105
+ name: 'address',
106
+ message: `Address to spend ${askForAmount} to?`,
107
+ type: 'input',
108
+ validate: input => !!input,
109
+ },
110
+ ({address}) => cbk(null, {address}));
111
+ }],
112
+
113
+ // Ask if this is the last output
114
+ askForAddition: [
115
+ 'askForAddress',
116
+ 'askForExternal',
117
+ ({askForExternal}, cbk) =>
118
+ {
119
+ // Exit early when the spend was to an internal addess
120
+ if (!askForExternal) {
121
+ return cbk();
122
+ }
123
+
124
+ return ask({
125
+ default: false,
126
+ name: 'add',
127
+ message: 'Add another spend to a different address?',
128
+ type: 'confirm',
129
+ },
130
+ ({add}) => cbk(null, add));
131
+ }],
132
+
133
+ // Create a decrease address to withdraw funds out to
134
+ createAddress: ['askForExternal', ({askForExternal}, cbk) => {
135
+ // Exit early when the withdraw address is external
136
+ if (askForExternal !== false) {
137
+ return cbk();
138
+ }
139
+
140
+ return createChainAddress({lnd}, cbk);
141
+ }],
142
+
143
+ // Final decrease output details
144
+ decrease: [
145
+ 'askForAddition',
146
+ 'askForAddress',
147
+ 'askForAmount',
148
+ 'createAddress',
149
+ 'getNetwork',
150
+ ({
151
+ askForAddition,
152
+ askForAddress,
153
+ askForAmount,
154
+ createAddress,
155
+ getNetwork,
156
+ },
157
+ cbk) =>
158
+ {
159
+ const {address} = (createAddress || askForAddress || {});
160
+
161
+ // Exit early when not decreasing to an address
162
+ if (!address) {
163
+ return cbk(null, {is_final: true, tokens: Number(askForAmount)});
164
+ }
165
+
166
+ // Make sure the address is valid
167
+ try {
168
+ toOutputScript(address, networks[getNetwork.bitcoinjs]);
169
+ } catch (err) {
170
+ return cbk([400, 'FailedToParseAddress', {err}]);
171
+ }
172
+
173
+ const output = toOutputScript(address, networks[getNetwork.bitcoinjs]);
174
+
175
+ return cbk(null, {
176
+ address,
177
+ is_final: !askForAddition,
178
+ output: bufferAsHex(output),
179
+ tokens: Number(askForAmount),
180
+ });
181
+ }],
182
+ },
183
+ returnResult({reject, resolve, of: 'decrease'}, cbk));
184
+ });
185
+ };
@@ -0,0 +1,171 @@
1
+ const {address} = require('bitcoinjs-lib');
2
+ const {networks} = require('bitcoinjs-lib');
3
+ const {Transaction} = require('bitcoinjs-lib');
4
+
5
+ const isOpenWitnessScript = require('./is_open_witness_script');
6
+
7
+ const bufferAsHex = buffer => buffer.toString('hex');
8
+ const {fromHex} = Transaction;
9
+ const hashFromTxId = id => Buffer.from(id, 'hex').reverse();
10
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
11
+ const {isArray} = Array;
12
+ const sequence = 0;
13
+ const {toOutputScript} = address;
14
+
15
+ /** Put together the replacement transaction
16
+
17
+ {
18
+ [add_funds_transaction_id]: <Add Funds Transaction Id Hex String>
19
+ [add_funds_transaction_vout]: <Add Funds Transaction Output Index Number>
20
+ bitcoinjs_network: <BitcoinJs Network Name String>
21
+ close_transaction: <Force Closed Transaction Hex String>
22
+ decrease: [{
23
+ output: <Output Script Hex Encoded String>
24
+ tokens: <Decrease By Tokens Number>
25
+ }]
26
+ funding_address: <Replacement Channel Funding Address String>
27
+ funding_tokens: <Replacement Channel Funding Tokens Number>
28
+ transaction_id: <Original Channel Funding Transaction Id Hex String>
29
+ transaction_vout: <Original Channel Funding Output Index Number>
30
+ }
31
+
32
+ @throws
33
+ <Error>
34
+
35
+ @returns
36
+ {
37
+ [add_funds_vin]: <Add Funds Input Index>
38
+ transaction: <Unsigned Transaction Hex String>
39
+ transaction_id: <Transaction Id Hex String>
40
+ transaction_vin: <Input Index of Original Channel Spend Number>
41
+ transaction_vout: <Replacement Channel Transaction Output Index Number>
42
+ }
43
+ */
44
+ module.exports = args => {
45
+ if (!args.bitcoinjs_network) {
46
+ throw new Error('ExpectedBitcoinJsNetworkNameToAssembleReplacementTx');
47
+ }
48
+
49
+ if (!args.close_transaction) {
50
+ throw new Error('ExpectedCloseTransactionToAssembleReplacementTx');
51
+ }
52
+
53
+ if (!isArray(args.decrease)) {
54
+ throw new Error('ExpectedArrayOfDecreasesToAssembleReplacementTx');
55
+ }
56
+
57
+ if (!args.funding_address) {
58
+ throw new Error('ExpectedFundingAddressToAssembleReplacementTx');
59
+ }
60
+
61
+ if (!args.funding_tokens) {
62
+ throw new Error('ExpectedFundingTokensToAssembleReplacementTx');
63
+ }
64
+
65
+ if (!args.transaction_id) {
66
+ throw new Error('ExpectedFundingTransactionIdToAssembleReplacementTx');
67
+ }
68
+
69
+ if (args.transaction_vout === undefined) {
70
+ throw new Error('ExpectedFundingTransactionVoutToAssembleReplacementTx');
71
+ }
72
+
73
+ const replacement = new Transaction();
74
+
75
+ // The replacement will spend the same input as the close transaction
76
+ const [{witness}, secondInput] = fromHex(args.close_transaction).ins;
77
+
78
+ // Confirm that the witness stack is a 2:2
79
+ if (!isOpenWitnessScript({script: bufferAsHex(witness.slice().pop())})) {
80
+ throw new Error('ExpectedMultiSigWitnessInCloseTransaction');
81
+ }
82
+
83
+ if (!!secondInput) {
84
+ throw new Error('ExpectedOnlySingleInputForCloseTransaction');
85
+ }
86
+
87
+ // The replacement will spend the funding outpoint
88
+ const hash = hashFromTxId(args.transaction_id);
89
+ const index = args.transaction_vout;
90
+
91
+ // The new channel multi-sig will be the replacement output
92
+ const network = networks[args.bitcoinjs_network];
93
+
94
+ // The replacement spends to the channel funding output
95
+ const fundingOutput = {
96
+ script: toOutputScript(args.funding_address, network),
97
+ tokens: args.funding_tokens,
98
+ };
99
+
100
+ // There can also be other outputs attached
101
+ const decreaseOutputs = args.decrease.map(({output, tokens}) => ({
102
+ tokens,
103
+ script: hexAsBuffer(output),
104
+ }));
105
+
106
+ const outputs = [].concat(decreaseOutputs).concat(fundingOutput);
107
+
108
+ // Sort outputs by BIP 69
109
+ outputs.sort((a, b) => {
110
+ // Sort by tokens ascending when no tie breaker needed
111
+ if (a.tokens !== b.tokens) {
112
+ return a.tokens - b.tokens;
113
+ }
114
+
115
+ // Otherwise compare output scripts lexicographically ascending
116
+ return a.script.compare(b.script);
117
+ });
118
+
119
+ const respendOutputIndex = outputs.findIndex(out => {
120
+ return out.script.equals(fundingOutput.script);
121
+ });
122
+
123
+ // Add the outputs to the replacement transaction
124
+ outputs.forEach(({script, tokens}) => replacement.addOutput(script, tokens));
125
+
126
+ // Exit early when not adding more inputs to the transaction
127
+ if (!args.add_funds_transaction_id) {
128
+ replacement.addInput(hash, index, sequence);
129
+
130
+ return {
131
+ transaction: replacement.toHex(),
132
+ transaction_id: replacement.getId(),
133
+ transaction_vin: replacement.ins.findIndex(input => {
134
+ return input.hash.equals(hash) && input.index === index;
135
+ }),
136
+ transaction_vout: respendOutputIndex,
137
+ };
138
+ }
139
+
140
+ // An additional input needs to be added when adding funds
141
+ const add = {
142
+ hash: hashFromTxId(args.add_funds_transaction_id),
143
+ index: args.add_funds_transaction_vout,
144
+ };
145
+
146
+ // Adding funds means spending the channel output plus the transit one
147
+ const spends = [{hash, index}, add];
148
+
149
+ // Sort the spending inputs for BIP 69
150
+ spends.sort((a, b) => {
151
+ const aHash = bufferAsHex(a.hash);
152
+ const bHash = bufferAsHex(b.hash);
153
+
154
+ return aHash.localeCompare(bHash) || a.index - b.index;
155
+ });
156
+
157
+ // Add the spends as inputs to the replacement tx
158
+ spends.forEach(({hash, index}) => replacement.addInput(hash, index));
159
+
160
+ return {
161
+ add_funds_vin: replacement.ins.findIndex(input => {
162
+ return input.hash.equals(add.hash) && input.index === add.index;
163
+ }),
164
+ transaction: replacement.toHex(),
165
+ transaction_id: replacement.getId(),
166
+ transaction_vin: replacement.ins.findIndex(input => {
167
+ return input.hash.equals(hash) && input.index === index;
168
+ }),
169
+ transaction_vout: respendOutputIndex,
170
+ };
171
+ };
@@ -0,0 +1,85 @@
1
+ const privateAsType = isPrivate => isPrivate ? 1 : 0;
2
+
3
+ /** Map change requests to requests considering local channels
4
+
5
+ {
6
+ channels: [{
7
+ capacity: <Channel Capacity Number>
8
+ [cooperative_close_address]: <Cooperative Close Locked Address String>
9
+ id: <Standard Format Channel Id String>
10
+ partner_public_key: <Peer Public Key Hex String>
11
+ }]
12
+ requests: [{
13
+ channel: <Standard Format Channel Id String>
14
+ [decrease]: <Reduce Capacity Tokens Number>
15
+ from: <From Node Public Key Id Hex String>
16
+ id: <Change Request Id Hex String>
17
+ [increase]: <Add Capacity Tokens Number>
18
+ [type]: <Intended Replacement Channel Channel Type Flags Number>
19
+ }]
20
+ }
21
+
22
+ @returns
23
+ {
24
+ requests: [{
25
+ [address]: <Cooperative Close Address String>
26
+ capacity: <Channel Capacity Tokens Number>
27
+ channel: <Standard Format Channel Id String>
28
+ [decrease]: <Reduce Capacity Tokens Number>
29
+ from: <From Node Public Key Id Hex String>
30
+ id: <Change Request Id Hex String>
31
+ [increase]: <Add Capacity Tokens Number>
32
+ [type]: <Change Channel Channel Type Flags Number>
33
+ }]
34
+ }
35
+ */
36
+ module.exports = ({channels, requests}) => {
37
+ // Change requests can only come from channels with the messaging peer
38
+ const capacityChangeRequests = requests.filter(request => {
39
+ const channel = channels.find(chan => chan.id === request.channel);
40
+
41
+ // Ignore requests from channels that aren't present
42
+ if (!channel) {
43
+ return false;
44
+ }
45
+
46
+ // The change request must come from the peer in the channel
47
+ return channel.partner_public_key === request.from;
48
+ });
49
+
50
+ const existing = {};
51
+
52
+ // Only consider one change request per channel
53
+ const changeRequests = capacityChangeRequests
54
+ .filter(({channel}) => {
55
+ // Exit early when the channel already has a change request
56
+ if (!!existing[channel]) {
57
+ return false;
58
+ }
59
+
60
+ // Do not allow more requests for this channel
61
+ existing[channel] = true;
62
+
63
+ return true;
64
+ })
65
+ .map(request => {
66
+ const channel = channels.find(chan => chan.id === request.channel);
67
+
68
+ const currentType = privateAsType(channel.is_private);
69
+
70
+ const type = request.type !== undefined && request.type !== currentType;
71
+
72
+ return {
73
+ address: channel.cooperative_close_address,
74
+ capacity: channel.capacity,
75
+ channel: channel.id,
76
+ decrease: request.decrease,
77
+ from: channel.partner_public_key,
78
+ id: request.id,
79
+ increase: request.increase,
80
+ type: type ? request.type : undefined,
81
+ };
82
+ });
83
+
84
+ return {requests: changeRequests};
85
+ };
@@ -0,0 +1,189 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncDetectSeries = require('async/detectSeries');
3
+ const asyncRetry = require('async/retry');
4
+ const {getIdentity} = require('ln-service');
5
+ const {getNodeAlias} = require('ln-sync');
6
+ const {returnResult} = require('asyncjs-util');
7
+ const {updateChannelFee} = require('ln-sync');
8
+
9
+ const acceptCapacityChange = require('./accept_capacity_change');
10
+ const getCapacityChangeRequests = require('./get_capacity_change_requests');
11
+ const initiateCapacityChange = require('./initiate_capacity_change');
12
+
13
+ const describeType = type => !(type & 0) ? 'private' : 'public';
14
+ const interval = 10 * 1000;
15
+ const peerName = ({alias, id}) => `${alias} ${id.substring(0, 8)}`.trim();
16
+ const times = 6 * 60 * 6;
17
+ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
18
+ const waitForCloseInterval = 1000 * 3;
19
+
20
+ /** Change a channel's capacity
21
+
22
+ {
23
+ ask: <Ask Function>
24
+ [delay]: <Wait Time For Incoming Capacity Requests Milliseconds Number>
25
+ lnd: <Authenticated LND API Object>
26
+ logger: <Winston Logger Object>
27
+ }
28
+ */
29
+ module.exports = ({ask, delay, lnd, logger}, cbk) => {
30
+ return new Promise((resolve, reject) => {
31
+ return asyncAuto({
32
+ // Check arguments
33
+ validate: cbk => {
34
+ if (!ask) {
35
+ return cbk([400, 'ExpectedAskFunctionToChangeCapacity']);
36
+ }
37
+
38
+ if (!lnd) {
39
+ return cbk([400, 'ExpectedLndToChangeCapacity']);
40
+ }
41
+
42
+ if (!logger) {
43
+ return cbk([400, 'ExpectedLoggerObjectToChangeCapacity']);
44
+ }
45
+
46
+ return cbk();
47
+ },
48
+
49
+ // Ask to confirm what will happen in a change capacity
50
+ askToConfirm: ['validate', ({}, cbk) => {
51
+ const details = [
52
+ 'This is experimental. Channels may force-close instead of change.',
53
+ 'Adding funds currently requires an extra confirmation wait.',
54
+ 'Chain fees will be deducted from the changed capacity total.',
55
+ 'Chain fees will be paid from the initiator local balance.',
56
+ 'Forwards with the peer will be halted during capacity change.',
57
+ ];
58
+
59
+ // Show the warnings
60
+ logger.info({details});
61
+
62
+ return ask({type: 'confirm', name: 'ok', message: 'OK?'}, ({ok}) => {
63
+ if (!ok) {
64
+ return cbk([400, 'CanceledChannelCapacityChange']);
65
+ }
66
+
67
+ return cbk();
68
+ });
69
+ }],
70
+
71
+ // Get the public key of this node
72
+ getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
73
+
74
+ // Wait for an incoming capacity change request
75
+ waitForProposal: ['validate', ({}, cbk) => {
76
+ return getCapacityChangeRequests({delay, lnd}, cbk);
77
+ }],
78
+
79
+ // Ask for acceptance of a channel
80
+ askToSelectChange: [
81
+ 'askToConfirm',
82
+ 'waitForProposal',
83
+ ({waitForProposal}, cbk) =>
84
+ {
85
+ return asyncDetectSeries(waitForProposal.requests, (request, cbk) => {
86
+ return getNodeAlias({lnd, id: request.from}, (err, res) => {
87
+ if (!!err) {
88
+ return cbk(err);
89
+ }
90
+
91
+ const change = !!request.increase ? 'Increase' : 'Decrease';
92
+ const delta = request.decrease || request.increase;
93
+ const hasType = request.type !== undefined;
94
+ const id = request.channel;
95
+ const peer = peerName(res);
96
+ const size = tokensAsBigUnit(request.capacity);
97
+
98
+
99
+ const action = `${change} capacity ${size} channel ${id}`;
100
+ const by = !!delta ? ` by ${tokensAsBigUnit(delta)}` : '';
101
+ const type = hasType ? describeType(request.type) : '';
102
+
103
+ const changeType = hasType ? ` and make channel ${type}` : '';
104
+
105
+ return ask({
106
+ type: 'confirm',
107
+ name: 'accept',
108
+ message: `${action} with ${peer}${by}${changeType}?`,
109
+ },
110
+ ({accept}) => {
111
+ if (!accept) {
112
+ return cbk(null, false);
113
+ }
114
+
115
+ // Fail changing when cooperative_close_address is set
116
+ if (!!request.address && !!request.increase) {
117
+ return cbk([400, 'ChannelHasLockedCoopCloseAddressSet']);
118
+ }
119
+
120
+ return cbk(null, true);
121
+ });
122
+ });
123
+ },
124
+ cbk);
125
+ }],
126
+
127
+ // Accept a capacity change proposal
128
+ acceptChange: ['askToSelectChange', ({askToSelectChange}, cbk) => {
129
+ // Exit early when there is no change proposal to accept
130
+ if (!askToSelectChange) {
131
+ return cbk();
132
+ }
133
+
134
+ return acceptCapacityChange({
135
+ lnd,
136
+ logger,
137
+ channel: askToSelectChange.channel,
138
+ from: askToSelectChange.from,
139
+ id: askToSelectChange.id,
140
+ increase: askToSelectChange.increase,
141
+ },
142
+ cbk);
143
+ }],
144
+
145
+ // Initiate a capacity change
146
+ initiateChange: ['askToSelectChange', ({askToSelectChange}, cbk) => {
147
+ if (!!askToSelectChange) {
148
+ return cbk();
149
+ }
150
+
151
+ return initiateCapacityChange({ask, lnd, logger}, cbk);
152
+ }],
153
+
154
+ // Reset the channel routing policies
155
+ adjustPolicy: [
156
+ 'acceptChange',
157
+ 'getIdentity',
158
+ 'initiateChange',
159
+ ({acceptChange, getIdentity, initiateChange}, cbk) =>
160
+ {
161
+ const policy = acceptChange || initiateChange;
162
+
163
+ logger.info({restoring_routing_policy: true});
164
+
165
+ return asyncRetry({interval, times}, cbk => {
166
+ return updateChannelFee({
167
+ lnd,
168
+ base_fee_mtokens: policy.base_fee_mtokens,
169
+ cltv_delta: policy.cltv_delta,
170
+ fee_rate: policy.fee_rate,
171
+ from: getIdentity.public_key,
172
+ transaction_id: policy.transaction_id,
173
+ transaction_vout: policy.transaction_vout,
174
+ },
175
+ cbk);
176
+ },
177
+ cbk);
178
+ }],
179
+
180
+ // Capacity change complete
181
+ complete: ['adjustPolicy', ({}, cbk) => {
182
+ logger.info({channel_capacity_successfully_changed: true});
183
+
184
+ return cbk();
185
+ }],
186
+ },
187
+ returnResult({reject, resolve}, cbk));
188
+ });
189
+ };
@@ -0,0 +1,62 @@
1
+ const {encodeBigSize} = require('bolt01');
2
+ const {rawChanId} = require('bolt07');
3
+
4
+ const channelFlagsType = '5';
5
+ const channelIdRecordType = '2';
6
+ const decreaseRecordType = '3';
7
+ const increaseRecordType = '4';
8
+ const requestIdRecordType = '1';
9
+ const typeAsHex = type => Buffer.from([type]).toString('hex');
10
+
11
+ /** Encode a request to change a channel capacity
12
+
13
+ {
14
+ channel: <Channel to Change Standard Format Id String>
15
+ [decrease]: <Remove Channel Funds By Tokens Number>
16
+ id: <Request Id Hex String>
17
+ increase: <Add Channel Funds By Tokens Number>
18
+ type: <New Channel Type Number>
19
+ }
20
+
21
+ @returns
22
+ {
23
+ records: [{
24
+ type: <Record Type Number String>
25
+ value: <Record Value Hex Encoded String>
26
+ }]
27
+ }
28
+ */
29
+ module.exports = ({channel, decrease, id, increase, type}) => {
30
+ const records = [
31
+ {
32
+ type: requestIdRecordType,
33
+ value: id,
34
+ },
35
+ {
36
+ type: channelIdRecordType,
37
+ value: rawChanId({channel}).id,
38
+ },
39
+ {
40
+ type: channelFlagsType,
41
+ value: typeAsHex(type),
42
+ },
43
+ ];
44
+
45
+ if (!!decrease) {
46
+ // Add a record reflecting the decrease in the capacity
47
+ records.push({
48
+ type: decreaseRecordType,
49
+ value: encodeBigSize({number: decrease.toString()}).encoded,
50
+ });
51
+ }
52
+
53
+ if (!!increase) {
54
+ // Add a record reflecting the increase in the capacity
55
+ records.push({
56
+ type: increaseRecordType,
57
+ value: encodeBigSize({number: increase.toString()}).encoded,
58
+ });
59
+ }
60
+
61
+ return {records};
62
+ };