docs: add official CrowdNode docs to help output
[crowdnode.js/.git] / bin / crowdnode.js
index b3c096f461642b7ffbe42ef5fb4eb1ce3bbe044c..adb6306402070cf5a6c2f3637e55cee0db490aae 100755 (executable)
@@ -49,6 +49,11 @@ let keysDirRel = `~/${configdir}/keys`;
 let shadowPath = Path.join(HOME, `${configdir}/shadow`);
 let defaultWifPath = Path.join(HOME, `${configdir}/default`);
 
+function debug() {
+  //@ts-ignore
+  console.error.apply(console, arguments);
+}
+
 function showVersion() {
   console.info(`${pkg.name} v${pkg.version} - ${pkg.description}`);
   console.info();
@@ -113,6 +118,19 @@ function showHelp() {
     "    crowdnode http SetReferral ./privkey.wif <referral-id> <signature>",
   );
   console.info("");
+  console.info("Official CrowdNode Resources");
+  console.info("");
+  console.info("Homepage:");
+  console.info("    https://crowdnode.io/");
+  console.info("");
+  console.info("Terms of Service:");
+  console.info("    https://crowdnode.io/terms/");
+  console.info("");
+  console.info("BlockChain API Guide:");
+  console.info(
+    "    https://knowledge.crowdnode.io/en/articles/5963880-blockchain-api-guide",
+  );
+  console.info("");
 }
 
 let cmds = {};
@@ -182,7 +200,7 @@ async function main() {
   }
 
   if ("list" === subcommand) {
-    await listKeys({ dashApi }, args);
+    await listKeys({ dashApi, defaultAddr }, args);
     process.exit(0);
     return;
   }
@@ -206,7 +224,8 @@ async function main() {
   }
 
   if ("import" === subcommand) {
-    await importKey(null, args);
+    let keypath = args.shift() || "";
+    await importKey({ keypath });
     process.exit(0);
     return;
   }
@@ -319,13 +338,20 @@ async function main() {
   // keeping rm for backwards compat
   if ("rm" === subcommand || "delete" === subcommand) {
     await initCrowdNode(insightBaseUrl);
-    await removeKey({ defaultAddr, dashApi, insightBaseUrl }, args);
+    let [addr, filepath] = await mustGetAddr({ defaultAddr }, args);
+    await removeKey({ addr, dashApi, filepath, insightBaseUrl }, args);
     process.exit(0);
     return;
   }
 
   if ("balance" === subcommand) {
-    await getBalance({ dashApi, defaultAddr }, args);
+    if (args.length) {
+      await getBalance({ dashApi, defaultAddr }, args);
+      process.exit(0);
+      return;
+    }
+
+    await getAllBalances({ dashApi, defaultAddr }, args);
     process.exit(0);
     return;
   }
@@ -393,7 +419,8 @@ async function stakeDash(
   let err = await Fs.access(args[0]).catch(Object);
   let addr;
   if (!err) {
-    addr = await importKey(null, [args[0]]);
+    let keypath = args.shift() || "";
+    addr = await importKey({ keypath });
   } else if (forceGenerate) {
     addr = await generateKey({ defaultKey: defaultAddr }, []);
   } else {
@@ -449,14 +476,14 @@ async function stakeDash(
 
   if (!state.status?.accept) {
     if (!state.status?.signup) {
-      await sendSignup({ dashApi, defaultAddr: addr, insightBaseUrl }, []);
+      await sendSignup({ dashApi, defaultAddr: addr, insightBaseUrl }, [addr]);
     }
-    await acceptTerms({ dashApi, defaultAddr: addr, insightBaseUrl }, []);
+    await acceptTerms({ dashApi, defaultAddr: addr, insightBaseUrl }, [addr]);
   }
 
   await depositDash(
     { dashApi, defaultAddr: addr, insightBaseUrl, noReserve },
-    args,
+    [addr].concat(args),
   );
 }
 
@@ -722,7 +749,7 @@ async function mustGetDefaultWif(defaultAddr, opts) {
   }
   if (defaultWif && !shownDefault) {
     shownDefault = true;
-    console.info(`Selected default staking key ${defaultAddr}`);
+    debug(`Selected default staking key ${defaultAddr}`);
     return defaultWif;
   }
 
@@ -882,11 +909,10 @@ async function promptPassphrase() {
 
 /**
  * Import and Encrypt
- * @param {Null} _
- * @param {Array<String>} args
+ * @param {Object} opts
+ * @param {String} opts.keypath
  */
-async function importKey(_, args) {
-  let keypath = args.shift() || "";
+async function importKey({ keypath }) {
   let key = await maybeReadKeyFileRaw(keypath);
   if (!key?.wif) {
     console.error(`no key found for '${keypath}'`);
@@ -1295,20 +1321,89 @@ async function setDefault(_, args) {
 /**
  * @param {Object} opts
  * @param {any} opts.dashApi - TODO
+ * @param {String} opts.defaultAddr
  * @param {Array<String>} args
  */
-async function listKeys({ dashApi }, args) {
+async function listKeys({ dashApi, defaultAddr }, args) {
   let wifnames = await listManagedKeynames();
 
+  if (wifnames) {
+    // to print 'default staking key' message
+    await mustGetAddr({ defaultAddr }, args);
+  }
+
   /**
    * @type Array<{ node: String, error: Error }>
    */
   let warns = [];
-  console.info(``);
-  console.info(`🔑Holdings 🪧 Stakings 💸Earnings`);
-  console.info(``);
-  console.info(`Staking keys: (in ${keysDirRel}/)`);
-  console.info(``);
+  // console.error because console.debug goes to stdout, not stderr
+  debug(``);
+  debug(`Staking keys: (in ${keysDirRel}/)`);
+  debug(``);
+
+  await wifnames.reduce(async function (promise, wifname) {
+    await promise;
+
+    let wifpath = Path.join(keysDir, wifname);
+    let addr = await maybeReadKeyFile(wifpath, { wif: false }).catch(function (
+      err,
+    ) {
+      warns.push({ node: wifname, error: err });
+      return "";
+    });
+    if (!addr) {
+      return;
+    }
+
+    console.info(`${addr}`);
+  }, Promise.resolve());
+  debug(``);
+
+  if (warns.length) {
+    console.warn(`Warnings:`);
+    warns.forEach(function (warn) {
+      console.warn(`${warn.node}: ${warn.error.message}`);
+    });
+    console.warn(``);
+  }
+}
+
+/**
+ * @param {Object} opts
+ * @param {any} opts.dashApi - TODO
+ * @param {String} opts.defaultAddr
+ * @param {Array<String>} args
+ */
+async function getAllBalances({ dashApi, defaultAddr }, args) {
+  let wifnames = await listManagedKeynames();
+  let totals = {
+    key: 0,
+    stake: 0,
+    dividend: 0,
+    keyDash: "",
+    stakeDash: "",
+    dividendDash: "",
+  };
+
+  if (wifnames.length) {
+    // to print 'default staking key' message
+    await mustGetAddr({ defaultAddr }, args);
+  }
+
+  /**
+   * @type Array<{ node: String, error: Error }>
+   */
+  let warns = [];
+  // console.error because console.debug goes to stdout, not stderr
+  debug(``);
+  debug(`Staking keys: (in ${keysDirRel}/)`);
+  debug(``);
+  console.info(
+    `|                                    |   🔑 Holdings |   🪧  Stakings |   💸 Earnings |`,
+  );
+  console.info(
+    `| ---------------------------------: | ------------: | ------------: | ------------: |`,
+  );
   if (!wifnames.length) {
     console.info(`    (none)`);
   }
@@ -1341,7 +1436,7 @@ async function listKeys({ dashApi }, args) {
     }
     */
 
-    process.stdout.write(`${addr}: `);
+    process.stdout.write(`| ${addr} |`);
 
     let balanceInfo = await dashApi.getInstantBalance(addr);
     let balanceDASH = toDASH(balanceInfo.balanceSat);
@@ -1357,12 +1452,26 @@ async function listKeys({ dashApi }, args) {
     let crowdNodeDivNum = toDuff(crowdNodeBalance.TotalDividend);
     let crowdNodeDivDASH = toDASH(crowdNodeDivNum);
     process.stdout.write(
-      `${balanceDASH}🔑 ${crowdNodeDASH}🪧 ${crowdNodeDivDASH}💸`,
+      ` ${balanceDASH} | ${crowdNodeDASH} | ${crowdNodeDivDASH} |`,
     );
 
+    totals.key += balanceInfo.balanceSat;
+    totals.dividend += crowdNodeBalance.TotalDividend;
+    totals.stake += crowdNodeBalance.TotalBalance;
+
     console.info();
   }, Promise.resolve());
-  console.info(``);
+  console.info(
+    `|                                    |               |               |               |`,
+  );
+  let total = `|                             Totals`;
+  totals.keyDash = toDASH(toDuff(totals.key.toString()));
+  totals.stakeDash = toDASH(toDuff(totals.stake.toString()));
+  totals.dividendDash = toDASH(toDuff(totals.dividend.toString()));
+  console.info(
+    `${total} | ${totals.stakeDash} | ${totals.stakeDash} | ${totals.dividendDash} |`,
+  );
+  debug(``);
 
   if (warns.length) {
     console.warn(`Warnings:`);
@@ -1387,12 +1496,12 @@ function isNamedLikeKey(name) {
 /**
  * @param {Object} opts
  * @param {any} opts.dashApi - TODO
- * @param {String} opts.defaultAddr
+ * @param {String} opts.addr
+ * @param {String} opts.filepath
  * @param {String} opts.insightBaseUrl
  * @param {Array<String>} args
  */
-async function removeKey({ dashApi, defaultAddr, insightBaseUrl }, args) {
-  let [addr, name] = await mustGetAddr({ defaultAddr }, args);
+async function removeKey({ addr, dashApi, filepath, insightBaseUrl }, args) {
   let balanceInfo = await dashApi.getInstantBalance(addr);
 
   let balanceDash = toDash(balanceInfo.balanceSat);
@@ -1433,10 +1542,10 @@ async function removeKey({ dashApi, defaultAddr, insightBaseUrl }, args) {
   }
 
   let wifname = await findWif(addr);
-  let filepath = Path.join(keysDir, wifname);
-  let wif = await maybeReadKeyPaths(name, { wif: true });
+  let fullpath = Path.join(keysDir, wifname);
+  let wif = await maybeReadKeyPaths(filepath, { wif: true });
 
-  await Fs.unlink(filepath).catch(function (err) {
+  await Fs.unlink(fullpath).catch(function (err) {
     console.error(`could not remove ${filepath}: ${err.message}`);
     process.exit(1);
   });
@@ -1453,7 +1562,7 @@ async function removeKey({ dashApi, defaultAddr, insightBaseUrl }, args) {
     console.info(``);
   } else {
     let newAddr = wifnames[0];
-    console.info(`Selected ${newAddr} as new default staking key.`);
+    debug(`Selected ${newAddr} as new default staking key.`);
     await Fs.writeFile(defaultWifPath, addr.replace(".wif", ""), "utf8");
     console.info(``);
   }
@@ -1976,7 +2085,7 @@ function toDash(duffs) {
  */
 function toDASH(duffs) {
   let dash = (duffs / DUFFS).toFixed(8);
-  return `Đ${dash}`.padStart(13, " ");
+  return `Đ` + dash.padStart(12, " ");
 }
 
 /**