ln-service 59.3.2 → 59.3.3

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.
@@ -2,13 +2,14 @@ const {equal} = require('node:assert').strict;
2
2
  const test = require('node:test');
3
3
 
4
4
  const asyncRetry = require('async/retry');
5
- const {address} = require('bitcoinjs-lib');
5
+ const {componentsOfTransaction} = require('@alexbosworth/blockchain');
6
6
  const {controlBlock} = require('p2tr');
7
7
  const {createPsbt} = require('psbt');
8
+ const {decodeBech32Address} = require('@alexbosworth/blockchain');
8
9
  const {decodePsbt} = require('psbt');
9
10
  const {hashForTree} = require('p2tr');
10
- const {leafHash} = require('p2tr');
11
- const {networks} = require('bitcoinjs-lib');
11
+ const {idForTransaction} = require('@alexbosworth/blockchain');
12
+ const {p2wpkhOutputScript} = require('@alexbosworth/blockchain');
12
13
  const {pointAdd} = require('tiny-secp256k1');
13
14
  const {privateAdd} = require('tiny-secp256k1');
14
15
  const {scriptElementsAsScript} = require('@alexbosworth/blockchain');
@@ -16,7 +17,8 @@ const {signHash} = require('p2tr');
16
17
  const {signSchnorr} = require('tiny-secp256k1');
17
18
  const {spawnLightningCluster} = require('ln-docker-daemons');
18
19
  const tinysecp = require('tiny-secp256k1');
19
- const {Transaction} = require('bitcoinjs-lib');
20
+ const {transactionFromComponents} = require('@alexbosworth/blockchain');
21
+ const {v1HashToSign} = require('p2tr');
20
22
  const {v1OutputScript} = require('p2tr');
21
23
 
22
24
  const {broadcastChainTransaction} = require('./../../');
@@ -28,24 +30,29 @@ const {getUtxos} = require('./../../');
28
30
  const {sendToChainAddress} = require('./../../');
29
31
  const {signPsbt} = require('./../../');
30
32
 
33
+ const bufferAsHex = buffer => buffer.toString('hex');
31
34
  const chainAddressRowType = 'chain_address';
32
35
  const compile = elements => scriptElementsAsScript({elements}).script;
36
+ const componentsOfTx = tx => componentsOfTransaction({transaction: tx});
33
37
  const confirmationCount = 6;
34
38
  const count = 100;
35
39
  const defaultInternalKey = '0350929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
40
+ const defaultLocktime = 0;
41
+ const defaultSequence = 0xffffffff;
42
+ const defaultTxVersion = 1;
36
43
  const description = 'description';
44
+ const emptyScriptSig = '';
37
45
  const {from} = Buffer;
38
- const {fromBech32} = address;
39
- const {fromHex} = Transaction;
40
- const {fromOutputScript} = address;
41
46
  const hexAsBuffer = hex => Buffer.from(hex, 'hex');
47
+ const idForTx = transaction => idForTransaction({transaction}).id;
42
48
  const interval = retryCount => 10 * Math.pow(2, retryCount);
43
49
  const OP_CHECKSIG = 172;
50
+ const p2wpkh = hash => p2wpkhOutputScript({hash}).script;
44
51
  const regtestBech32AddressHrp = 'bcrt';
45
52
  const smallTokens = 2e5;
46
53
  const times = 20;
47
- const {toOutputScript} = address;
48
54
  const tokens = 1e6;
55
+ const transactionSighashDefault = 0;
49
56
  const txIdHexByteLength = 64;
50
57
 
51
58
  // Funding a transaction should result in a funded PSBT
@@ -107,11 +114,11 @@ test(`Fund PSBT`, async () => {
107
114
 
108
115
  equal(output.tokens, tokens, 'Got expected tokens output');
109
116
 
110
- const {data, version} = fromBech32(address);
117
+ const {program, version} = decodeBech32Address({address});
111
118
 
112
119
  const prefix = `${Buffer.from([version]).toString('hex')}14`;
113
120
 
114
- const expectedOutput = `${prefix}${data.toString('hex')}`;
121
+ const expectedOutput = `${prefix}${bufferAsHex(program)}`;
115
122
 
116
123
  equal(output.output_script, expectedOutput, 'Got expected output script');
117
124
 
@@ -127,9 +134,9 @@ test(`Fund PSBT`, async () => {
127
134
  try {
128
135
  await generate({count});
129
136
 
130
- const keyPair1 = ecp.makeRandom({network: networks.regtest});
131
- const keyPair2 = ecp.makeRandom({network: networks.regtest});
132
- const unusedKey = ecp.makeRandom({network: networks.regtest});
137
+ const keyPair1 = ecp.makeRandom({});
138
+ const keyPair2 = ecp.makeRandom({});
139
+ const unusedKey = ecp.makeRandom({});
133
140
 
134
141
  const witnessScript = compile([
135
142
  from(unusedKey.publicKey).slice(1),
@@ -168,31 +175,42 @@ test(`Fund PSBT`, async () => {
168
175
  // Send the tx to the chain
169
176
  await broadcastChainTransaction({lnd, transaction: signed.transaction});
170
177
 
171
- // Make a new tx that will spend the output back into the wallet
172
- const tx = new Transaction();
178
+ const spends = componentsOfTx(signed.transaction).outputs;
173
179
 
174
180
  // The new tx spends the Taproot output
175
- tx.addInput(
176
- fromHex(signed.transaction).getHash(),
177
- fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
178
- );
181
+ const inputs = [{
182
+ id: idForTx(signed.transaction),
183
+ script: emptyScriptSig,
184
+ sequence: defaultSequence,
185
+ vout: spends.findIndex(n => n.tokens === tokens),
186
+ }];
179
187
 
180
188
  // Make an output to pay back into the wallet
181
- const chainOutput = toOutputScript(
182
- (await createChainAddress({lnd})).address,
183
- networks.regtest
184
- );
189
+ const chainAddress = await createChainAddress({lnd});
190
+
191
+ const {program} = decodeBech32Address({address: chainAddress.address});
185
192
 
186
193
  // Add output to the pay back transaction
187
- tx.addOutput(chainOutput, smallTokens);
188
-
189
- const [hashToSign] = tx.ins.map((input, i) => {
190
- return tx.hashForWitnessV1(
191
- i,
192
- [hexAsBuffer(output.script)],
193
- [tokens],
194
- Transaction.SIGHASH_DEFAULT,
195
- );
194
+ const outputs = [{
195
+ script: bufferAsHex(p2wpkh(program)),
196
+ tokens: smallTokens,
197
+ }];
198
+
199
+ // Make a new tx that will spend the output back into the wallet
200
+ const unsigned = transactionFromComponents({
201
+ inputs,
202
+ outputs,
203
+ locktime: defaultLocktime,
204
+ version: defaultTxVersion,
205
+ });
206
+
207
+ const [hashToSign] = inputs.map((input, i) => {
208
+ return v1HashToSign({
209
+ sighash: transactionSighashDefault,
210
+ spends: [{tokens, script: output.script}],
211
+ transaction: unsigned.transaction,
212
+ vin: i,
213
+ }).hash;
196
214
  });
197
215
 
198
216
  // Ready for private key combining
@@ -205,22 +223,33 @@ test(`Fund PSBT`, async () => {
205
223
  hash,
206
224
  private_key: Buffer.from(combinedKey).toString('hex'),
207
225
  public_key: Buffer.from(combinedPoint).toString('hex'),
208
- sign_hash: hashToSign.toString('hex'),
226
+ sign_hash: hashToSign,
209
227
  });
210
228
 
211
- const signature = hexAsBuffer(signedInput.signature);
212
-
213
- // Add the signature to the input
214
- tx.ins.forEach((input, i) => tx.setWitness(i, [signature]));
229
+ const {signature} = signedInput;
230
+
231
+ // Add the signature to the input and serialize the signed transaction
232
+ const {transaction} = transactionFromComponents({
233
+ outputs,
234
+ inputs: inputs.map(input => ({
235
+ id: input.id,
236
+ script: input.script,
237
+ sequence: input.sequence,
238
+ vout: input.vout,
239
+ witness: [signature],
240
+ })),
241
+ locktime: defaultLocktime,
242
+ version: defaultTxVersion,
243
+ });
215
244
 
216
- await broadcastChainTransaction({lnd, transaction: tx.toHex()});
245
+ await broadcastChainTransaction({lnd, transaction});
217
246
 
218
247
  await asyncRetry({interval, times}, async () => {
219
248
  await generate({});
220
249
 
221
250
  const {utxos} = await getUtxos({lnd});
222
251
 
223
- const utxo = utxos.find(n => n.transaction_id === tx.getId());
252
+ const utxo = utxos.find(n => n.transaction_id === idForTx(transaction));
224
253
 
225
254
  if (!utxo || !utxo.confirmation_count) {
226
255
  throw new Error('ExpectedReceivedTaprootSpend');
@@ -234,7 +263,7 @@ test(`Fund PSBT`, async () => {
234
263
  try {
235
264
  await generate({count});
236
265
 
237
- const keyPair = ecp.makeRandom({network: networks.regtest});
266
+ const keyPair = ecp.makeRandom({});
238
267
 
239
268
  const witnessScript = compile([
240
269
  from(keyPair.publicKey.slice(1)),
@@ -264,35 +293,50 @@ test(`Fund PSBT`, async () => {
264
293
  // Send the tx to the chain
265
294
  await broadcastChainTransaction({lnd, transaction: signed.transaction});
266
295
 
267
- // Make a new tx that will spend the output back into the wallet
268
- const tx = new Transaction();
296
+ const spends = componentsOfTx(signed.transaction).outputs;
269
297
 
270
298
  // The new tx spends the Taproot output
271
- tx.addInput(
272
- fromHex(signed.transaction).getHash(),
273
- fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
274
- );
299
+ const inputs = [{
300
+ id: idForTx(signed.transaction),
301
+ script: emptyScriptSig,
302
+ sequence: defaultSequence,
303
+ vout: spends.findIndex(n => n.tokens === tokens),
304
+ }];
275
305
 
276
306
  // Make an output to pay back into the wallet
277
- const chainOutput = toOutputScript(
278
- (await createChainAddress({lnd})).address,
279
- networks.regtest
280
- );
307
+ const chainAddress = await createChainAddress({lnd});
308
+
309
+ const {program} = decodeBech32Address({address: chainAddress.address});
281
310
 
282
311
  // Add output to the pay back transaction
283
- tx.addOutput(chainOutput, smallTokens);
284
-
285
- const [hashToSign] = tx.ins.map((input, i) => {
286
- return tx.hashForWitnessV1(
287
- i,
288
- [hexAsBuffer(output.script)],
289
- [tokens],
290
- Transaction.SIGHASH_DEFAULT,
291
- hexAsBuffer(leafHash({script: witnessScript}).hash),
292
- );
312
+ const outputs = [{
313
+ script: bufferAsHex(p2wpkh(program)),
314
+ tokens: smallTokens,
315
+ }];
316
+
317
+ // Make a new tx that will spend the output back into the wallet
318
+ const unsigned = transactionFromComponents({
319
+ inputs,
320
+ outputs,
321
+ locktime: defaultLocktime,
322
+ version: defaultTxVersion,
293
323
  });
294
324
 
295
- const signature = from(signSchnorr(hashToSign, from(keyPair.privateKey)));
325
+ const [hashToSign] = inputs.map((input, i) => {
326
+ return v1HashToSign({
327
+ leaf: {script: witnessScript},
328
+ sighash: transactionSighashDefault,
329
+ spends: [{tokens, script: output.script}],
330
+ transaction: unsigned.transaction,
331
+ vin: i,
332
+ }).hash;
333
+ });
334
+
335
+ const hashBuffer = hexAsBuffer(hashToSign);
336
+
337
+ const schnorrSig = signSchnorr(hashBuffer, from(keyPair.privateKey));
338
+
339
+ const signature = bufferAsHex(from(schnorrSig));
296
340
 
297
341
  const {block} = controlBlock({
298
342
  external_key: output.external_key,
@@ -300,23 +344,28 @@ test(`Fund PSBT`, async () => {
300
344
  script_branches: branches,
301
345
  });
302
346
 
303
- // Add the signature to the input
304
- tx.ins.forEach((input, i) => {
305
- return tx.setWitness(i, [
306
- signature,
307
- hexAsBuffer(witnessScript),
308
- hexAsBuffer(block),
309
- ]);
347
+ // Add the signature to the input and serialize the signed transaction
348
+ const {transaction} = transactionFromComponents({
349
+ outputs,
350
+ inputs: inputs.map(input => ({
351
+ id: input.id,
352
+ script: input.script,
353
+ sequence: input.sequence,
354
+ vout: input.vout,
355
+ witness: [signature, witnessScript, block],
356
+ })),
357
+ locktime: defaultLocktime,
358
+ version: defaultTxVersion,
310
359
  });
311
360
 
312
- await broadcastChainTransaction({lnd, transaction: tx.toHex()});
361
+ await broadcastChainTransaction({lnd, transaction});
313
362
 
314
363
  await asyncRetry({interval, times}, async () => {
315
364
  await generate({});
316
365
 
317
366
  const {utxos} = await getUtxos({lnd});
318
367
 
319
- const utxo = utxos.find(n => n.transaction_id === tx.getId());
368
+ const utxo = utxos.find(n => n.transaction_id === idForTx(transaction));
320
369
 
321
370
  if (!utxo || !utxo.confirmation_count) {
322
371
  throw new Error('ExpectedReceivedTaprootSpend');
@@ -334,19 +383,17 @@ test(`Fund PSBT`, async () => {
334
383
  try {
335
384
  await generate({count});
336
385
 
337
- const keyPair = ecp.makeRandom({network: networks.regtest});
386
+ const keyPair = ecp.makeRandom({});
338
387
 
339
388
  const output = v1OutputScript({
340
389
  internal_key: from(keyPair.publicKey).toString('hex'),
341
390
  });
342
391
 
343
- const outputScript = hexAsBuffer(output.script);
344
-
345
392
  const [utxo] = (await getUtxos({lnd})).utxos.reverse();
346
393
 
347
394
  // Make a PSBT paying to the Taproot output
348
395
  const {psbt} = createPsbt({
349
- outputs: [{tokens, script: outputScript.toString('hex')}],
396
+ outputs: [{tokens, script: output.script}],
350
397
  utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
351
398
  });
352
399
 
@@ -359,52 +406,74 @@ test(`Fund PSBT`, async () => {
359
406
  // Send the tx to the chain
360
407
  await broadcastChainTransaction({lnd, transaction: signed.transaction});
361
408
 
362
- // Make a new tx that will spend the output back into the wallet
363
- const tx = new Transaction();
409
+ const spends = componentsOfTx(signed.transaction).outputs;
364
410
 
365
411
  // The new tx spends the Taproot output
366
- tx.addInput(
367
- fromHex(signed.transaction).getHash(),
368
- fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
369
- );
412
+ const inputs = [{
413
+ id: idForTx(signed.transaction),
414
+ script: emptyScriptSig,
415
+ sequence: defaultSequence,
416
+ vout: spends.findIndex(n => n.tokens === tokens),
417
+ }];
370
418
 
371
419
  // Make an output to pay back into the wallet
372
- const chainOutput = toOutputScript(
373
- (await createChainAddress({lnd})).address,
374
- networks.regtest
375
- );
420
+ const chainAddress = await createChainAddress({lnd});
421
+
422
+ const {program} = decodeBech32Address({address: chainAddress.address});
376
423
 
377
424
  // Add output to the pay back transaction
378
- tx.addOutput(chainOutput, smallTokens);
379
-
380
- const [hashToSign] = tx.ins.map((input, i) => {
381
- return tx.hashForWitnessV1(
382
- i,
383
- [outputScript],
384
- [tokens],
385
- Transaction.SIGHASH_DEFAULT,
386
- );
425
+ const outputs = [{
426
+ script: bufferAsHex(p2wpkh(program)),
427
+ tokens: smallTokens,
428
+ }];
429
+
430
+ // Make a new tx that will spend the output back into the wallet
431
+ const unsigned = transactionFromComponents({
432
+ inputs,
433
+ outputs,
434
+ locktime: defaultLocktime,
435
+ version: defaultTxVersion,
436
+ });
437
+
438
+ const [hashToSign] = inputs.map((input, i) => {
439
+ return v1HashToSign({
440
+ sighash: transactionSighashDefault,
441
+ spends: [{tokens, script: output.script}],
442
+ transaction: unsigned.transaction,
443
+ vin: i,
444
+ }).hash;
387
445
  });
388
446
 
389
447
  const signedInput = signHash({
390
448
  private_key: from(keyPair.privateKey).toString('hex'),
391
449
  public_key: from(keyPair.publicKey).toString('hex'),
392
- sign_hash: hashToSign.toString('hex'),
450
+ sign_hash: hashToSign,
393
451
  });
394
452
 
395
- const signature = hexAsBuffer(signedInput.signature);
396
-
397
- // Add the signature to the input
398
- tx.ins.forEach((input, i) => tx.setWitness(i, [Buffer.from(signature)]));
453
+ const {signature} = signedInput;
454
+
455
+ // Add the signature to the input and serialize the signed transaction
456
+ const {transaction} = transactionFromComponents({
457
+ outputs,
458
+ inputs: inputs.map(input => ({
459
+ id: input.id,
460
+ script: input.script,
461
+ sequence: input.sequence,
462
+ vout: input.vout,
463
+ witness: [signature],
464
+ })),
465
+ locktime: defaultLocktime,
466
+ version: defaultTxVersion,
467
+ });
399
468
 
400
- await broadcastChainTransaction({lnd, transaction: tx.toHex()});
469
+ await broadcastChainTransaction({lnd, transaction});
401
470
 
402
471
  await asyncRetry({interval, times}, async () => {
403
472
  await generate({});
404
473
 
405
474
  const {utxos} = await getUtxos({lnd});
406
475
 
407
- const utxo = utxos.find(n => n.transaction_id === tx.getId());
476
+ const utxo = utxos.find(n => n.transaction_id === idForTx(transaction));
408
477
 
409
478
  if (!utxo || !utxo.confirmation_count) {
410
479
  throw new Error('ExpectedReceivedTaprootSpend');
@@ -1,143 +0,0 @@
1
- const {join} = require('path');
2
- const {spawn} = require('child_process');
3
-
4
- const asyncAuto = require('async/auto');
5
- const openPortFinder = require('portfinder');
6
- const {networks} = require('bitcoinjs-lib');
7
- const {payments} = require('bitcoinjs-lib');
8
- const tinysecp = require('tiny-secp256k1');
9
-
10
- const knownDaemons = ['btcd'];
11
- const localhost = '127.0.0.1';
12
- const notFoundIndex = -1;
13
- const {p2pkh} = payments;
14
- const rpcServerReady = /RPC.server.listening/;
15
- const unableToStartServer = /Unable.to.start.server/;
16
-
17
- /** Start a BTCSuite Type Daemon
18
-
19
- {
20
- daemon: <Daemon Name String>
21
- dir: <Data Directory String>
22
- [is_tls]: <Uses TLS Bool>
23
- mining_public_key: <Mining Public Key Hex String>
24
- }
25
-
26
- @returns
27
- <Daemon Object>
28
-
29
- @returns via cbk
30
- {
31
- daemon: <Daemon Child Process Object>
32
- listen_port: <Listen Port Number>
33
- rpc_cert: <RPC Cert Path String>
34
- rpc_port: <RPC Port Number>
35
- }
36
- */
37
- module.exports = (args, cbk) => {
38
- return asyncAuto({
39
- // Import ECPair library
40
- ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
41
-
42
- // Check arguments
43
- validate: cbk => {
44
- if (knownDaemons.indexOf(args.daemon) === notFoundIndex) {
45
- return cbk([400, 'ExpectedBtcsuiteDaemonName', args.daemon]);
46
- }
47
-
48
- if (!args.dir) {
49
- return cbk([400, 'ExpectedDirectoryForDaemon']);
50
- }
51
-
52
- if (!args.mining_public_key) {
53
- return cbk([400, 'ExpectedMiningPublicKeyForDaemon']);
54
- }
55
-
56
- return cbk();
57
- },
58
-
59
- // Get an open P2P listen port
60
- listenPort: ['validate', ({}, cbk) => {
61
- const port = 14567 + Math.round(Math.random() * 1000);
62
-
63
- const stopPort = port + 1000;
64
-
65
- return openPortFinder.getPort({port, stopPort}, (err, port) => {
66
- if (!!err) {
67
- return cbk([500, 'FailedToFindOpenPortForListenPort', err]);
68
- }
69
-
70
- return cbk(null, port);
71
- });
72
- }],
73
-
74
- // Get an open RPC listen port
75
- rpcPort: ['listenPort', ({}, cbk) => {
76
- const port = 14567 + Math.round(Math.random() * 1000);
77
-
78
- const stopPort = port + 1000;
79
-
80
- return openPortFinder.getPort({port, stopPort}, (err, port) => {
81
- if (!!err) {
82
- return cbk([500, 'FailedToFindOpenPortForRpc', err]);
83
- }
84
-
85
- return cbk(null, port);
86
- });
87
- }],
88
-
89
- // Spin up the chain daemon
90
- spawnDaemon: [
91
- 'ecp',
92
- 'listenPort',
93
- 'rpcPort',
94
- ({ecp, listenPort, rpcPort}, cbk) =>
95
- {
96
- const miningKey = Buffer.from(args.mining_public_key, 'hex');
97
- const network = networks.testnet;
98
-
99
- const pubkey = ecp.fromPublicKey(miningKey, network).publicKey;
100
-
101
- const daemon = spawn(args.daemon, [
102
- '--datadir', args.dir,
103
- '--debuglevel', 'trace',
104
- '--listen', `${localhost}:${listenPort}`,
105
- '--logdir', args.dir,
106
- '--miningaddr', p2pkh({network, pubkey}).address,
107
- (!args.is_tls ? '--notls' : null),
108
- '--regtest',
109
- '--relaynonstd',
110
- '--rpccert', join(args.dir, 'rpc.cert'),
111
- '--rpckey', join(args.dir, 'rpc.key'),
112
- '--rpclisten', `${localhost}:${rpcPort}`,
113
- '--rpcpass', '0k39BVOdg4uuS7qNCG2jbIXNpwU7d3Ft87PpHPPoCfk=',
114
- '--rpcuser', 'bitcoinrpc',
115
- '--txindex',
116
- ]);
117
-
118
- daemon.stdout.on('data', data => {
119
- if (unableToStartServer.test(`${data}`)) {
120
- return cbk([503, 'SpawnDaemonFailure', `${data}`, args]);
121
- }
122
-
123
- if (rpcServerReady.test(`${data}`)) {
124
- return cbk(null, daemon);
125
- }
126
-
127
- return;
128
- });
129
- }],
130
- },
131
- (err, res) => {
132
- if (!!err) {
133
- return cbk(err);
134
- }
135
-
136
- return cbk(null, {
137
- daemon: res.spawnDaemon,
138
- listen_port: res.listenPort,
139
- rpc_cert: join(args.dir, 'rpc.cert'),
140
- rpc_port: res.rpcPort,
141
- });
142
- });
143
- };
@@ -1,102 +0,0 @@
1
- const {encode} = require('varuint-bitcoin');
2
-
3
- const {address} = require('bitcoinjs-lib');
4
- const {crypto} = require('bitcoinjs-lib');
5
- const {networks} = require('bitcoinjs-lib');
6
- const {payments} = require('bitcoinjs-lib');
7
- const {script} = require('bitcoinjs-lib');
8
- const {Transaction} = require('bitcoinjs-lib');
9
-
10
- const scriptBufAsScript = require('./script_buffers_as_script');
11
-
12
- const defaultNetwork = 'testnet';
13
- const encodeSignature = script.signature.encode;
14
- const hexBase = 16;
15
- const {p2pkh} = payments;
16
- const {SIGHASH_ALL} = Transaction;
17
- const {toOutputScript} = address;
18
-
19
- /** Create a transaction that sends coins to a destination chain address
20
-
21
- {
22
- destination: <Destination Address String>
23
- ecp: <ECPair Object>
24
- fee: <Fee Tokens To Remove From Spend Number>
25
- private_key: <WIF Serialized Private Key String>
26
- spend_transaction_id: <Transaction Id to Spend Hex String>
27
- spend_vout: <Vout to Spend Number>
28
- tokens: <Tokens to Spend Number>
29
- }
30
-
31
- @returns
32
- {
33
- transaction: <Transaction Hex Serialized String>
34
- }
35
- */
36
- module.exports = args => {
37
- if (!args.destination) {
38
- throw new Error('ExpectedDestinationAddressToSendTokensTo');
39
- }
40
-
41
- if (!args.ecp) {
42
- throw new Error('ExpectedEcpairObjectToChainSendTransaction');
43
- }
44
-
45
- if (!args.private_key) {
46
- throw new Error('ExpectedPrivateKeyToAuthorizeSend');
47
- }
48
-
49
- if (!args.spend_transaction_id) {
50
- throw new Error('ExpectedOutpointTxIdToSpend');
51
- }
52
-
53
- if (args.spend_vout === undefined) {
54
- throw new Error('ExpectedOutpointVoutToSpend');
55
- }
56
-
57
- if (!args.tokens) {
58
- throw new Error('ExpectedTokenCountToSend');
59
- }
60
-
61
- const network = networks[defaultNetwork];
62
-
63
- const keyPair = args.ecp.fromWIF(args.private_key, network);
64
- const outputScript = toOutputScript(args.destination, network);
65
- const tx = new Transaction();
66
-
67
- const outpointHash = Buffer.from(args.spend_transaction_id, 'hex').reverse();
68
-
69
- tx.addInput(outpointHash, args.spend_vout);
70
-
71
- try {
72
- tx.addOutput(outputScript, (args.tokens - (args.fee || 0)));
73
- } catch (err) {
74
- throw new Error('ErrorAddingOutputToSendOnChainTransaction');
75
- }
76
-
77
- const sigHashAll = parseInt(SIGHASH_ALL, hexBase);
78
-
79
- [keyPair].forEach((signingKey, vin) => {
80
- const flag = sigHashAll;
81
- const {publicKey} = signingKey;
82
-
83
- const scriptPub = p2pkh({network, pubkey: publicKey}).output;
84
-
85
- const sigHash = tx.hashForSignature(vin, scriptPub, flag);
86
-
87
- const signature = signingKey.sign(sigHash);
88
-
89
- const sig = encodeSignature(signature, flag);
90
-
91
- const sigPush = Buffer.concat([encode(sig.length), sig]);
92
- const pubKeyPush = Buffer.concat([encode(publicKey.length), publicKey]);
93
-
94
- const scriptSig = Buffer.concat([sigPush, pubKeyPush]);
95
-
96
- tx.setInputScript(vin, scriptSig);
97
-
98
- return;
99
- });
100
-
101
- return {transaction: tx.toHex()};
102
- };