paid-services 3.4.0 → 3.7.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 (38) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +16 -0
  3. package/capacity/accept_capacity_change.js +2 -0
  4. package/capacity/ask_for_change_details.js +41 -9
  5. package/capacity/ask_for_decrease.js +167 -49
  6. package/capacity/capacity_change_requests.js +9 -0
  7. package/capacity/change_channel_capacity.js +7 -1
  8. package/capacity/encode_change_request.js +8 -1
  9. package/capacity/fee_for_replacement.js +3 -7
  10. package/capacity/get_capacity_change_requests.js +1 -0
  11. package/capacity/get_capacity_replacement.js +56 -7
  12. package/capacity/initiate_capacity_change.js +1 -2
  13. package/capacity/parse_capacity_change_request.js +8 -1
  14. package/capacity/propose_capacity_change.js +208 -92
  15. package/capacity/propose_channel_open.js +54 -0
  16. package/package.json +9 -9
  17. package/services/response_for_relay.js +0 -1
  18. package/test/capacity/test_capacity_change_requests.js +1 -0
  19. package/test/capacity/test_encode_change_request.js +5 -0
  20. package/test/capacity/test_parse_capacity_change_request.js +1 -0
  21. package/test/integration/test_accept_capacity_change.js +3 -4
  22. package/test/integration/test_change_channel_capacity.js +8 -1
  23. package/test/integration/test_decrease_channel_capacity.js +23 -8
  24. package/test/integration/test_increase_channel_capacity.js +4 -1
  25. package/test/integration/test_split_channel_capacity.js +241 -0
  26. package/trades/accept_trade.js +12 -3
  27. package/trades/create_trade.js +82 -50
  28. package/trades/decode_anchored_trade.js +56 -0
  29. package/trades/encode_anchored_trade.js +35 -0
  30. package/trades/finalize_trade_secret.js +7 -0
  31. package/trades/find_trade.js +28 -3
  32. package/trades/manage_trade.js +15 -4
  33. package/trades/manage_trades.js +209 -2
  34. package/trades/request_trade_by_id.js +7 -1
  35. package/trades/service_anchored_trades.js +181 -0
  36. package/trades/service_open_trade.js +172 -0
  37. package/trades/service_trade_requests.js +106 -35
  38. package/trades/trade_from_invoice.js +51 -0
@@ -0,0 +1,54 @@
1
+ const {acceptsChannelOpen} = require('ln-sync');
2
+ const asyncAuto = require('async/auto');
3
+ const {connectPeer} = require('ln-sync');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ /** Check that a channel open would be accepted by a node
7
+
8
+ {
9
+ capacity: <Capacity Tokens Number>
10
+ id: <Node Identity Public Key Hex String>
11
+ lnd: <Authenticated LND API Object>
12
+ }
13
+
14
+ @returns via cbk or Promise
15
+ {
16
+ is_accepted: <Channel Proposal Is Accepted Bool>
17
+ }
18
+ */
19
+ module.exports = ({capacity, id, lnd}, cbk) => {
20
+ return new Promise((resolve, reject) => {
21
+ return asyncAuto({
22
+ // Check arguments
23
+ validate: cbk => {
24
+ if (!capacity) {
25
+ return cbk([400, 'ExpectedCapacityTokensToProposeChannelOpen']);
26
+ }
27
+
28
+ if (!id) {
29
+ return cbk([400, 'ExpectedNodePublicKeyToProposeChannelOpen']);
30
+ }
31
+
32
+ if (!lnd) {
33
+ return cbk([400, 'ExpectedAuthenticatedLndToProposeChannelOpen']);
34
+ }
35
+
36
+ return cbk();
37
+ },
38
+
39
+ // Connect to the peer
40
+ connect: ['validate', ({}, cbk) => connectPeer({id, lnd}, cbk)],
41
+
42
+ // Propose a channel open to check acceptance
43
+ checkAccept: ['connect', ({}, cbk) => {
44
+ return acceptsChannelOpen({
45
+ capacity,
46
+ lnd,
47
+ partner_public_key: id,
48
+ },
49
+ cbk);
50
+ }],
51
+ },
52
+ returnResult({reject, resolve, of: 'checkAccept'}, cbk));
53
+ });
54
+ };
package/package.json CHANGED
@@ -7,20 +7,20 @@
7
7
  "url": "https://github.com/alexbosworth/paid-services/issues"
8
8
  },
9
9
  "dependencies": {
10
- "async": "3.2.2",
11
- "asyncjs-util": "1.2.7",
10
+ "async": "3.2.3",
11
+ "asyncjs-util": "1.2.8",
12
12
  "bolt01": "1.2.3",
13
13
  "bolt07": "1.8.0",
14
- "invoices": "2.0.2",
15
- "ln-service": "53.2.0",
16
- "ln-sync": "3.6.0"
14
+ "invoices": "2.0.3",
15
+ "ln-service": "53.4.0",
16
+ "ln-sync": "3.6.1"
17
17
  },
18
18
  "description": "Lightning Paid Services library",
19
19
  "devDependencies": {
20
20
  "@alexbosworth/tap": "15.0.10",
21
- "ln-docker-daemons": "2.1.0",
21
+ "ln-docker-daemons": "2.2.0",
22
22
  "mock-lnd": "1.4.1",
23
- "secp256k1": "4.0.2"
23
+ "secp256k1": "4.0.3"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=12.20"
@@ -37,8 +37,8 @@
37
37
  "url": "https://github.com/alexbosworth/paid-services.git"
38
38
  },
39
39
  "scripts": {
40
- "integration-tests": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 120 test/integration/*.js",
40
+ "integration-tests": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
41
41
  "test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/capacity/*.js test/client/*.js test/config/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/server/*.js test/services/*.js test/trades/*.js"
42
42
  },
43
- "version": "3.4.0"
43
+ "version": "3.7.0"
44
44
  }
@@ -18,7 +18,6 @@ const defaultProbeTimeoutMs = 1000 * 60 * 5;
18
18
  const expiresAt = () => new Date(Date.now() + 1000 * 60 * 30).toISOString();
19
19
  const interval = 1000 * 60 * 10;
20
20
  const rateDivisor = BigInt(1e6);
21
- const sumOf = arr => arr.reduce((sum, n) => BigInt(n) + n, BigInt(0));
22
21
  const text = 'Relay payment request created';
23
22
  const times = 12;
24
23
 
@@ -34,6 +34,7 @@ const tests = [
34
34
  from: Buffer.alloc(33, 3).toString('hex'),
35
35
  id: Buffer.alloc(32).toString('hex'),
36
36
  increase: undefined,
37
+ type: undefined,
37
38
  }],
38
39
  },
39
40
  },
@@ -8,6 +8,7 @@ const makeArgs = overrides => {
8
8
  decrease: 0,
9
9
  id: Buffer.alloc(32).toString('hex'),
10
10
  increase: undefined,
11
+ type: 1,
11
12
  };
12
13
 
13
14
  Object.keys(overrides).forEach(k => args[k] = overrides[k]);
@@ -29,6 +30,10 @@ const tests = [
29
30
  type: '2',
30
31
  value: '0000000000000000',
31
32
  },
33
+ {
34
+ type: '5',
35
+ value: '01',
36
+ },
32
37
  ],
33
38
  },
34
39
  },
@@ -31,6 +31,7 @@ const tests = [
31
31
  channel: '102x1x0',
32
32
  from: '0218819f4e0dbab6c6bc434913bace0b69f20d832f68934acd72fc610a9b76fe30',
33
33
  id: '2bf4a6bf1b97aff5bbb760f1aa7a5705c885194b4e29d42e5576498107f02361',
34
+ type: undefined,
34
35
  },
35
36
  },
36
37
  },
@@ -32,7 +32,6 @@ const maturity = 100;
32
32
  const proposeCapacityChange = propose;
33
33
  const signCapacityReplacement = method;
34
34
  const size = 2;
35
- const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
36
35
  const times = 2000;
37
36
  const {toOutputScript} = address;
38
37
  const weightAsVBytes = n => Math.ceil(n / 4);
@@ -61,15 +60,15 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
61
60
 
62
61
  // Wait for the channel to be active
63
62
  const channel = await asyncRetry({interval, times}, async () => {
64
- const [channel] = (await getChannels({lnd, is_active: true})).channels;
63
+ const [channel] = (await getChannels({lnd})).channels;
65
64
 
66
- if (!!channel) {
65
+ if (!!channel && !!channel.is_active) {
67
66
  return channel;
68
67
  }
69
68
 
70
69
  await generate({});
71
70
 
72
- throw new Error('ExpectedChannelActivation');
71
+ throw new Error('ExpectedInitialChannelActivation');
73
72
  });
74
73
 
75
74
  // Find the open tx
@@ -27,7 +27,6 @@ const log = () => {};
27
27
  const maturity = 100;
28
28
  const size = 2;
29
29
  const slow = 5000;
30
- const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
31
30
  const times = 2000;
32
31
  const {toOutputScript} = address;
33
32
  const weightAsVBytes = n => Math.ceil(n / 4);
@@ -88,6 +87,10 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
88
87
  return cbk({amount: '0'});
89
88
  }
90
89
 
90
+ if (args.name === 'decrease') {
91
+ return cbk({decrease: 'internal_spend_funds'});
92
+ }
93
+
91
94
  if (args.name === 'direction') {
92
95
  return cbk({direction: 'decrease'});
93
96
  }
@@ -104,6 +107,10 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
104
107
  return cbk({query: target.id});
105
108
  }
106
109
 
110
+ if (args.name === 'type') {
111
+ return cbk({type: args.default});
112
+ }
113
+
107
114
  throw new Error('UnknownQueryNameForInitiator');
108
115
  },
109
116
  logger: {error: log, info: log},
@@ -27,7 +27,6 @@ const logger = {error: () => {}, info: () => {}};
27
27
  const maturity = 100;
28
28
  const size = 2;
29
29
  const slow = 3000;
30
- const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
31
30
  const times = 2000;
32
31
  const {toOutputScript} = address;
33
32
  const weightAsVBytes = n => Math.ceil(n / 4);
@@ -47,11 +46,13 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
47
46
 
48
47
  try {
49
48
  // Open up a new channel
50
- const channelOpen = await openChannel({
51
- lnd,
52
- local_tokens: capacity,
53
- partner_public_key: target.id,
54
- partner_socket: target.socket,
49
+ const channelOpen = await asyncRetry({interval, times}, async () => {
50
+ return await openChannel({
51
+ lnd,
52
+ local_tokens: capacity,
53
+ partner_public_key: target.id,
54
+ partner_socket: target.socket,
55
+ });
55
56
  });
56
57
 
57
58
  // Wait for the channel to be active
@@ -88,6 +89,10 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
88
89
  return cbk({amount: '200000'});
89
90
  }
90
91
 
92
+ if (args.name === 'decrease') {
93
+ return cbk({decrease: 'internal_spend_funds'});
94
+ }
95
+
91
96
  if (args.name === 'direction') {
92
97
  return cbk({direction: 'decrease'});
93
98
  }
@@ -116,6 +121,10 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
116
121
  return cbk({query: target.id});
117
122
  }
118
123
 
124
+ if (args.name === 'type') {
125
+ return cbk({type: 'Private'});
126
+ }
127
+
119
128
  throw new Error('UnexpectedQueryNameForProposingSide');
120
129
  },
121
130
  });
@@ -124,7 +133,9 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
124
133
  // Wait to see the inbound proposal
125
134
  waitForProposal: async () => {
126
135
  await asyncRetry({interval, times}, async () => {
127
- const {requests} = await getCapacityChangeRequests({lnd: target.lnd});
136
+ const {requests} = await getCapacityChangeRequests({
137
+ lnd: target.lnd,
138
+ });
128
139
 
129
140
  if (!requests.length) {
130
141
  throw new Error('FailedToFindChangeRequest');
@@ -159,7 +170,9 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
159
170
  interval: slow,
160
171
  },
161
172
  async () => {
162
- const [channel] = (await getChannels({lnd, is_active: true})).channels.filter(n => n.capacity < capacity);
173
+ const {channels} = await getChannels({lnd, is_active: true});
174
+
175
+ const [channel] = channels.filter(n => n.capacity < capacity);
163
176
 
164
177
  await generate({});
165
178
 
@@ -167,6 +180,8 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
167
180
  throw new Error('ExpectedChannelActivation');
168
181
  }
169
182
 
183
+ equal(channel.is_private, true, 'Channel is changed to private');
184
+
170
185
  {
171
186
  const {policies} = await getChannel({lnd, id: channel.id});
172
187
 
@@ -26,7 +26,6 @@ const interval = 10;
26
26
  const logger = {error: () => {}, info: () => {}};
27
27
  const maturity = 100;
28
28
  const size = 2;
29
- const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
30
29
  const times = 2000;
31
30
  const {toOutputScript} = address;
32
31
  const weightAsVBytes = n => Math.ceil(n / 4);
@@ -111,6 +110,10 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
111
110
  return cbk({query: target.id});
112
111
  }
113
112
 
113
+ if (args.name === 'type') {
114
+ return cbk({type: args.default});
115
+ }
116
+
114
117
  throw new Error('UnexpectedQueryNameForProposingSide');
115
118
  },
116
119
  });
@@ -0,0 +1,241 @@
1
+ const {addPeer} = require('ln-service');
2
+ const {address} = require('bitcoinjs-lib');
3
+ const asyncAuto = require('async/auto');
4
+ const asyncMap = require('async/map');
5
+ const asyncRetry = require('async/retry');
6
+ const {broadcastChainTransaction} = require('ln-service');
7
+ const {closeChannel} = require('ln-service');
8
+ const {createChainAddress} = require('ln-service');
9
+ const {getChainTransactions} = require('ln-service');
10
+ const {getChannel} = require('ln-service');
11
+ const {getChannels} = require('ln-service');
12
+ const {getNetwork} = require('ln-sync');
13
+ const {networks} = require('bitcoinjs-lib');
14
+ const {openChannel} = require('ln-service');
15
+ const {spawnLightningCluster} = require('ln-docker-daemons');
16
+ const {test} = require('@alexbosworth/tap');
17
+ const {Transaction} = require('bitcoinjs-lib');
18
+
19
+ const {changeChannelCapacity} = require('./../../capacity');
20
+ const {getCapacityChangeRequests} = require('./../../capacity');
21
+
22
+ const bufferAsHex = buffer => buffer.toString('hex');
23
+ const capacity = 1e6;
24
+ const {fromHex} = Transaction;
25
+ const id = Buffer.alloc(32).toString('hex');
26
+ const interval = 10;
27
+ const logger = {error: () => {}, info: () => {}};
28
+ const maturity = 100;
29
+ const size = 3;
30
+ const slow = 3000;
31
+ const splitCapacity = 200000;
32
+ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
33
+ const times = 2000;
34
+ const {toOutputScript} = address;
35
+ const weightAsVBytes = n => Math.ceil(n / 4);
36
+
37
+ // A capacity replacement proposal should be counter signed and accepted
38
+ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
39
+ const {kill, nodes} = await spawnLightningCluster({size});
40
+
41
+ const [control, target, remote] = nodes;
42
+
43
+ const {generate, lnd} = control;
44
+
45
+ const network = networks[(await getNetwork({lnd})).bitcoinjs];
46
+
47
+ // Make some funds
48
+ await generate({count: maturity});
49
+
50
+ // Add remote as a peer
51
+ await addPeer({lnd, public_key: remote.id, socket: remote.socket});
52
+
53
+ try {
54
+ // Open up a new channel
55
+ const channelOpen = await asyncRetry({interval, times}, async () => {
56
+ return await openChannel({
57
+ lnd,
58
+ local_tokens: capacity,
59
+ partner_public_key: target.id,
60
+ partner_socket: target.socket,
61
+ });
62
+ });
63
+
64
+ // Wait for the channel to be active
65
+ const channel = await asyncRetry({interval, times}, async () => {
66
+ const [channel] = (await getChannels({lnd, is_active: true})).channels;
67
+
68
+ if (!channel) {
69
+ await generate({});
70
+
71
+ throw new Error('ExpectedChannelActivation');
72
+ }
73
+
74
+ const {policies} = await getChannel({lnd, id: channel.id});
75
+
76
+ const noPolicy = policies.find(n => !n.cltv_delta);
77
+
78
+ if (!!channel && !noPolicy) {
79
+ return channel;
80
+ }
81
+
82
+ await generate({});
83
+
84
+ throw new Error('ExpectedChannelActivation');
85
+ });
86
+
87
+ await asyncAuto({
88
+ // Propose the channel
89
+ propose: async () => {
90
+ await changeChannelCapacity({
91
+ lnd,
92
+ logger,
93
+ ask: (args, cbk) => {
94
+ if (args.name === 'add') {
95
+ return cbk({add: false});
96
+ }
97
+
98
+ if (args.name === 'amount') {
99
+ return cbk({amount: splitCapacity});
100
+ }
101
+
102
+ if (args.name === 'decrease') {
103
+ return cbk({decrease: 'open_channel'});
104
+ }
105
+
106
+ if (args.name === 'direction') {
107
+ return cbk({direction: 'decrease'});
108
+ }
109
+
110
+ if (args.name === 'internal') {
111
+ return cbk({internal: true});
112
+ }
113
+
114
+ if (args.name === 'key') {
115
+ return cbk({key: remote.id});
116
+ }
117
+
118
+ if (args.name === 'ok') {
119
+ return cbk({ok: true});
120
+ }
121
+
122
+ if (args.name === 'proceed') {
123
+ return cbk({proceed: true});
124
+ }
125
+
126
+ if (args.name === 'rate') {
127
+ return cbk({rate: args.default});
128
+ }
129
+
130
+ if (args.name === 'spend') {
131
+ return cbk({spend: false});
132
+ }
133
+
134
+ if (args.name === 'query') {
135
+ return cbk({query: target.id});
136
+ }
137
+
138
+ if (args.name === 'type') {
139
+ return cbk({type: 'Private'});
140
+ }
141
+
142
+ throw new Error('UnexpectedQueryNameForProposingSide');
143
+ },
144
+ });
145
+ },
146
+
147
+ // Wait to see the inbound proposal
148
+ waitForProposal: async () => {
149
+ await asyncRetry({interval, times}, async () => {
150
+ const {requests} = await getCapacityChangeRequests({
151
+ lnd: target.lnd,
152
+ });
153
+
154
+ if (!requests.length) {
155
+ throw new Error('FailedToFindChangeRequest');
156
+ }
157
+ });
158
+ },
159
+
160
+ // Accept the channel
161
+ accept: ['waitForProposal', async ({}) => {
162
+ await changeChannelCapacity({
163
+ logger,
164
+ ask: (args, cbk) => {
165
+ if (args.name === 'accept') {
166
+ return cbk({accept: true});
167
+ }
168
+
169
+ if (args.name === 'ok') {
170
+ return cbk({ok: true});
171
+ }
172
+
173
+ throw new Error('UnexpectedQueryNameForAcceptingSide');
174
+ },
175
+ lnd: target.lnd,
176
+ });
177
+ }],
178
+
179
+ // Generate to confirm the change
180
+ generate: ['waitForProposal', async ({}) => {
181
+ // Wait for the new channel to be active
182
+ const recreated = await asyncRetry({
183
+ times,
184
+ interval: slow,
185
+ },
186
+ async () => {
187
+ const {channels} = await getChannels({lnd, is_active: true});
188
+
189
+ const [channel] = channels
190
+ .filter(n => n.is_private)
191
+ .filter(n => n.capacity < capacity);
192
+
193
+ await generate({});
194
+
195
+ if (!channel) {
196
+ throw new Error('ExpectedChannelActivation');
197
+ }
198
+
199
+ const channelWithRemote = channels.find(channel => {
200
+ return channel.partner_public_key === remote.id;
201
+ });
202
+
203
+ equal(channelWithRemote.capacity, splitCapacity, 'Remote created');
204
+
205
+ {
206
+ const {policies} = await getChannel({lnd, id: channel.id});
207
+
208
+ const noPolicy = policies.find(n => !n.cltv_delta);
209
+
210
+ if (!!noPolicy) {
211
+ throw new Error('ExpectedChannelPolicy');
212
+ }
213
+ };
214
+
215
+ {
216
+ const {policies} = await getChannel({
217
+ id: channel.id,
218
+ lnd: target.lnd,
219
+ });
220
+
221
+ const noPolicy = policies.find(n => !n.cltv_delta);
222
+
223
+ if (!!noPolicy) {
224
+ throw new Error('ExpectedChannelPolicy');
225
+ }
226
+ };
227
+
228
+ return;
229
+ });
230
+
231
+ return;
232
+ }],
233
+ });
234
+ } catch (err) {
235
+ strictSame(err, null, 'Expected no failure');
236
+ } finally {
237
+ await kill({});
238
+ }
239
+
240
+ return end();
241
+ });
@@ -1,6 +1,7 @@
1
1
  const asyncAuto = require('async/auto');
2
2
  const asyncEach = require('async/each');
3
3
  const {cancelHodlInvoice} = require('ln-service');
4
+ const {getInvoice} = require('ln-service');
4
5
  const {returnResult} = require('asyncjs-util');
5
6
  const {settleHodlInvoice} = require('ln-service');
6
7
 
@@ -10,13 +11,14 @@ const {isArray} = Array;
10
11
 
11
12
  {
12
13
  cancel: [<Alternative Invoice Id Hex String>]
14
+ id: <Trade Id Hex String>
13
15
  lnd: <Authenticated LND API Object>
14
16
  secret: <Invoice to Settle Preimage Hex String>
15
17
  }
16
18
 
17
19
  @returns via cbk or Promise
18
20
  */
19
- module.exports = ({cancel, lnd, secret}, cbk) => {
21
+ module.exports = ({cancel, id, lnd, secret}, cbk) => {
20
22
  return new Promise((resolve, reject) => {
21
23
  return asyncAuto({
22
24
  // Check arguments
@@ -25,6 +27,10 @@ module.exports = ({cancel, lnd, secret}, cbk) => {
25
27
  return cbk([400, 'ExpectedArrayOfIdsToCancelToAcceptTrade']);
26
28
  }
27
29
 
30
+ if (!id) {
31
+ return cbk([400, 'ExpectedAnchorTradeIdToAcceptTrade']);
32
+ }
33
+
28
34
  if (!lnd) {
29
35
  return cbk([400, 'ExpectedAuthenticatedLndToAcceptTrade']);
30
36
  }
@@ -36,9 +42,12 @@ module.exports = ({cancel, lnd, secret}, cbk) => {
36
42
  return cbk();
37
43
  },
38
44
 
45
+ // Fetch the anchor invoice to make sure it's still open
46
+ getAnchor: ['validate', ({}, cbk) => getInvoice({id, lnd}, cbk)],
47
+
39
48
  // Cancel alternative invoices so that only one resolves as settled
40
- cancel: ['validate', ({}, cbk) => {
41
- return asyncEach(cancel, (id, cbk) => {
49
+ cancel: ['getAnchor', ({}, cbk) => {
50
+ return asyncEach([].concat(cancel).concat(id), (id, cbk) => {
42
51
  return cancelHodlInvoice({id, lnd}, cbk);
43
52
  },
44
53
  cbk);