initial commit
[crowdnode.js/.git] / create-tx.js
1 #!/usr/bin/env node
2
3 "use strict";
4
5 require("dotenv").config({ path: ".env" });
6 require("dotenv").config({ path: ".env.secret" });
7
8 let privKey = process.env.PRIVATE_KEY;
9
10 let Fs = require("fs").promises;
11
12 let Dashcore = require("@dashevo/dashcore-lib");
13 let request = require("./lib/request.js");
14
15 let baseUrl = `https://insight.dash.org/insight-api`;
16
17 function help() {
18   console.info(``);
19   console.info(`Usage:`);
20   console.info(`        create-tx [key-file] <change> <address:amount,>`);
21   console.info(``);
22   console.info(`Example:`);
23   console.info(`        create-tx ./key.wif XkY...w6C Xkz...aDf:1000`);
24   console.info(``);
25 }
26
27 async function main() {
28   if (["help", "--help", "-h"].includes(process.argv[2])) {
29     help();
30     process.exit(0);
31     return;
32   }
33
34   let args = process.argv.slice(2);
35   if (!privKey) {
36     let keyFile = args.shift();
37     // TODO error handling
38     privKey = await Fs.readFile(keyFile, "ascii").trim();
39   }
40
41   let changeAddr = args.shift();
42   let payments = args.map(function (payment) {
43     let parts = payment.split(":");
44     if (2 !== parts.length) {
45       help();
46       process.exit(1);
47       return;
48     }
49     return {
50       address: parts[0],
51       // TODO check for bad input (i.e. decimal)
52       satoshis: parseInt(parts[1], 10),
53     };
54   });
55
56   // TODO check validity
57   if (!payments.length) {
58     help();
59     process.exit(1);
60     return;
61   }
62
63   let pk = new Dashcore.PrivateKey(privKey);
64   let pub = pk.toPublicKey().toAddress().toString();
65
66   /** @type InsightUtxo */
67   let utxoUrl = `${baseUrl}/addr/${pub}/utxo`;
68   let utxoResp = await request({ url: utxoUrl, json: true });
69
70   /** @type Array<Utxo> */
71   let utxos = [];
72
73   await utxoResp.body.reduce(async function (promise, utxo) {
74     await promise;
75
76     let txUrl = `${baseUrl}/tx/${utxo.txid}`;
77     let txResp = await request({ url: txUrl, json: true });
78     let data = txResp.body;
79
80     // TODO the ideal would be the smallest amount that is greater than the required amount
81
82     let utxoIndex = -1;
83     data.vout.some(function (vout, index) {
84       if (!vout.scriptPubKey?.addresses?.includes(utxo.address)) {
85         return false;
86       }
87
88       let satoshis = parseInt(vout.value[0] + vout.value.slice(2), 10);
89       if (utxo.satoshis !== satoshis) {
90         return false;
91       }
92
93       utxoIndex = index;
94       return true;
95     });
96
97     utxos.push({
98       txId: utxo.txid,
99       outputIndex: utxoIndex,
100       address: utxo.address,
101       script: utxo.scriptPubKey,
102       satoshis: utxo.satoshis,
103     });
104   }, Promise.resolve());
105
106   let tx = new Dashcore.Transaction().from(utxos);
107   tx.change(changeAddr);
108   payments.forEach(async function (payment) {
109     tx.to(payment.address, payment.satoshis);
110   });
111   tx.sign(pk);
112
113   // TODO get the *real* fee
114   console.warn('Fee:', tx.getFee());
115   console.info(tx.serialize());
116 }
117
118 main().catch(function (err) {
119   console.error("Fail:");
120   console.error(err.stack || err);
121 });