feat!: redenominate Dash/Duff and Percent/Permille for CLI, fix incorrect accept...
authorAJ ONeal <coolaj86@gmail.com>
Tue, 21 Jun 2022 05:20:00 +0000 (23:20 -0600)
committerAJ ONeal <coolaj86@gmail.com>
Tue, 21 Jun 2022 05:20:00 +0000 (23:20 -0600)
README.md
bin/crowdnode.js
cli/README.md
lib/crowdnode.js

index e9db85cf7c52c246accefd2fce94aef15ace9871..9a7240152ea2b06c1d456eb27cdd3e182d862b05 100644 (file)
--- a/README.md
+++ b/README.md
@@ -13,14 +13,16 @@ Blockchain API.
 
 You must have [node.js](https://webinstall.dev/node) installed:
 
+### Mac & Linux
+
 ```bash
-# Mac, Linux
 curl https://webinstall.dev/node | bash
 export PATH="${HOME}/.local/opt/node:$PATH"
 ```
 
+### Windows
+
 ```pwsh
-# Windows
 curl.exe -A MS https://webinstall.dev/node | powershell
 PATH %USERPROFILE%\.local\opt\node;%PATH%
 ```
@@ -36,6 +38,16 @@ npm install --save crowdnode@v1
 The SDK also provides Type Hinting via JSDoc (compatible with TypeScript / tsc
 without any transpiling).
 
+## IMPORTANT
+
+The CLI is denominated in **Dash** and **Percent** because those are the units
+most customers are familiar with and can easily calculate in their heads without
+making "careless" mistakes.
+
+HOWEVER, the SDK and CrowdNode API are denominated in **Duffs** (Satoshis) and
+**Permil** (Permille) because those are the whole units that are easiest to
+compute.
+
 ## QuickStart
 
 The CrowdNode SDK uses Dashcore to create raw transactions and broadcasts them
@@ -53,7 +65,7 @@ let CrowdNode = require("crowdnode");
 async function main() {
   let keyfile = process.argv[2];
 
-  // a wallet pre-loaded with about Đ0.001
+  // a wallet pre-loaded with about Đ0.01
   let wif = await Fs.readFile(keyfile, "utf8");
   wif = wif.trim();
 
@@ -146,6 +158,7 @@ await CrowdNode.accept(wif, hotwallet);
  * }
  */
 
+// amount given in DUFFs
 await CrowdNode.deposit(wif, hotwallet, (amount = 0));
 /** @type SocketPayment
  * {
@@ -157,6 +170,7 @@ await CrowdNode.deposit(wif, hotwallet, (amount = 0));
  * }
  */
 
+// permil is 1/10 percent, 500 permil = 50.0 percent
 await CrowdNode.withdrawal(wif, hotwallet, permil);
 /** @type SocketPayment
  * {
@@ -233,6 +247,15 @@ await CrowdNode.http.VotingOpen(pub);
 /* ${baseUrl}/VotingOpen/${pub} */
 ```
 
+## Glossary
+
+| Term          | Description                                                   |
+| ------------- | ------------------------------------------------------------- |
+| addr          | your Dash address (Base58Check-encoded Pay-to-PubKey Address) |
+| amount        | the integer value of "Duffs" (Đ/100000000)                    |
+| permil        | 1/1000, 1‰, or 0.1% - between 1 and 1000 (0.1% to 100.0%)     |
+| ./privkey.wif | the file path to your staking key in WIF (Base58Check) format |
+
 # CLI Documentation
 
 See <https://github.com/dashhive/crowdnode.js/tree/main/cli>.
index 500289b10bbc1ae76dfce6353353949bacf46228..81720cff6911fff7129732dbe957d333fd4b4ad2 100755 (executable)
@@ -25,18 +25,16 @@ let qrWidth = 2 + 67 + 2;
 //   0.00238608 // minimum recommended amount
 // Target:
 //   0.01000000
-let signupFees =
-  CrowdNode.requests.signupForApi +
-  CrowdNode.requests.acceptTerms +
-  2 * CrowdNode.requests.offset;
-let feeEstimate = 2 * 1000;
+let signupOnly = CrowdNode.requests.signupForApi + CrowdNode.requests.offset;
+let acceptOnly = CrowdNode.requests.acceptTerms + CrowdNode.requests.offset;
+let signupFees = signupOnly + acceptOnly;
+let feeEstimate = 500;
+let signupTotal = signupFees + 2 * feeEstimate;
 
-let signupTotal = signupFees + feeEstimate;
-
-function showQr(signupAddr, amount = 0) {
+function showQr(signupAddr, duffs = 0) {
   let signupUri = `dash://${signupAddr}`;
-  if (amount) {
-    signupUri += `?amount=${amount}`;
+  if (duffs) {
+    signupUri += `?amount=${duffs}`;
   }
 
   let signupQr = Qr.ascii(signupUri, { indent: 4 });
@@ -60,16 +58,20 @@ function showHelp() {
   console.info("    crowdnode status ./privkey.wif");
   console.info("    crowdnode signup ./privkey.wif");
   console.info("    crowdnode accept ./privkey.wif");
-  console.info("    crowdnode deposit ./privkey.wif [amount] [--no-reserve]");
   console.info(
-    "    crowdnode withdrawal ./privkey.wif <permil> # 1-1000 (1.0-100.0%)",
+    "    crowdnode deposit ./privkey.wif [dash-amount] [--no-reserve]",
+  );
+  console.info(
+    "    crowdnode withdrawal ./privkey.wif <percent> # 1.0-100.0 (steps by 0.1)",
   );
   console.info("");
 
   console.info("Helpful Extras:");
   console.info("    crowdnode generate [./privkey.wif]");
   console.info("    crowdnode balance ./privkey.wif");
-  console.info("    crowdnode transfer ./source.wif <key-file-or-pub-addr>");
+  console.info(
+    "    crowdnode transfer ./source.wif <key-file-or-pub-addr> [dash-amount]",
+  );
   console.info("");
 
   console.info("CrowdNode HTTP RPC:");
@@ -102,7 +104,7 @@ async function main() {
   // Usage:
   //    crowdnode <subcommand> [flags] <privkey> [options]
   // Example:
-  //    crowdnode withdrawal --unconfirmed ./Xxxxpubaddr.wif 1000
+  //    crowdnode withdrawal ./Xxxxpubaddr.wif 100.0
 
   let args = process.argv.slice(2);
 
@@ -190,7 +192,7 @@ async function main() {
   // deposit if balance is over 100,000 (0.00100000)
   process.stdout.write("Checking balance... ");
   let balanceInfo = await dashApi.getInstantBalance(pub);
-  console.info(`${balanceInfo.balanceSat} (${balanceInfo.balance})`);
+  console.info(`${balanceInfo.balanceSat} (Đ${balanceInfo.balance})`);
   /*
   let balanceInfo = await insightApi.getBalance(pub);
   if (balanceInfo.unconfirmedBalanceSat || balanceInfo.unconfirmedAppearances) {
@@ -331,17 +333,21 @@ async function balance(args, state) {
   return;
 }
 
+// ex: node ./bin/crowdnode.js transfer ./priv.wif 'pub' 0.01
 async function transfer(args, state) {
   let newAddr = await wifFileToAddr(process.argv[4]);
-  let amount = parseInt(process.argv[5] || 0, 10);
+  let dashAmount = parseFloat(process.argv[5] || 0);
+  let duffAmount = Math.round(dashAmount * DUFFS);
   let tx;
-  if (amount) {
-    tx = await state.dashApi.createPayment(state.privKey, newAddr, amount);
+  if (duffAmount) {
+    tx = await state.dashApi.createPayment(state.privKey, newAddr, duffAmount);
   } else {
     tx = await state.dashApi.createBalanceTransfer(state.privKey, newAddr);
   }
-  if (amount) {
-    console.info(`Transferring ${amount} to ${newAddr}...`);
+  if (duffAmount) {
+    console.info(
+      `Transferring ${duffAmount} (Đ${dashAmount}) to ${newAddr}...`,
+    );
   } else {
     console.info(`Transferring balance to ${newAddr}...`);
   }
@@ -385,7 +391,7 @@ async function signup(args, state) {
     return;
   }
 
-  let hasEnough = state.balanceInfo.balanceSat > signupTotal;
+  let hasEnough = state.balanceInfo.balanceSat > signupOnly + feeEstimate;
   if (!hasEnough) {
     await collectSignupFees(state.insightBaseUrl, state.pub);
   }
@@ -409,7 +415,7 @@ async function accept(args, state) {
     process.exit(0);
     return;
   }
-  let hasEnough = state.balanceInfo.balanceSat > signupTotal;
+  let hasEnough = state.balanceInfo.balanceSat > acceptOnly + feeEstimate;
   if (!hasEnough) {
     await collectSignupFees(state.insightBaseUrl, state.pub);
   }
@@ -432,10 +438,10 @@ async function deposit(args, state) {
 
   // this would allow for at least 2 withdrawals costing (21000 + 1000)
   let reserve = 50000;
-  let reserveDash = (reserve / DUFFS).toFixed(8);
+  let reserveDash = toDash(reserve);
   if (!state.noReserve) {
     console.info(
-      `reserving ${reserve} (${reserveDash}) for withdrawals (--no-reserve to disable)`,
+      `reserving ${reserve} (Đ${reserveDash}) for withdrawals (--no-reserve to disable)`,
     );
   } else {
     reserve = 0;
@@ -445,8 +451,9 @@ async function deposit(args, state) {
 
   // deposit what the user asks, or all that we have,
   // or all that the user deposits - but at least 2x the reserve
-  let desiredAmount = parseInt(args.shift() || 0, 10);
-  let effectiveAmount = desiredAmount;
+  let desiredAmountDash = parseFloat(args.shift() || 0);
+  let desiredAmountDuff = Math.round(desiredAmountDash * DUFFS);
+  let effectiveAmount = desiredAmountDuff;
   if (!effectiveAmount) {
     effectiveAmount = state.balanceInfo.balanceSat - reserve;
   }
@@ -454,25 +461,31 @@ async function deposit(args, state) {
 
   if (state.balanceInfo.balanceSat < needed) {
     let ask = 0;
-    if (desiredAmount) {
-      ask = desiredAmount + reserve + -state.balanceInfo.balanceSat;
+    if (desiredAmountDuff) {
+      ask = desiredAmountDuff + reserve + -state.balanceInfo.balanceSat;
     }
     await collectDeposit(state.insightBaseUrl, state.pub, ask);
     state.balanceInfo = await state.dashApi.getInstantBalance(state.pub);
     if (state.balanceInfo.balanceSat < needed) {
+      let balanceDash = toDash(state.balanceInfo.balanceSat);
       console.error(
-        `Balance is still too small: ${state.balanceInfo.balanceSat}`,
+        `Balance is still too small: ${state.balanceInfo.balanceSat} (Đ${balanceDash})`,
       );
       process.exit(1);
       return;
     }
   }
-  if (!desiredAmount) {
+  if (!desiredAmountDuff) {
     effectiveAmount = state.balanceInfo.balanceSat - reserve;
   }
 
-  console.info(`(holding ${reserve} in reserve for API calls)`);
-  console.info(`Initiating deposit of ${effectiveAmount}...`);
+  console.info(
+    `(holding ${reserve} (Đ${reserveDash}) in reserve for API calls)`,
+  );
+  let effectiveDash = toDash(effectiveAmount);
+  console.info(
+    `Initiating deposit of ${effectiveAmount} (Đ${effectiveDash})...`,
+  );
   await CrowdNode.deposit(state.privKey, state.hotwallet, effectiveAmount);
   state.deposit = "✅";
   console.info(`    ${state.deposit} DepositReceived`);
@@ -487,10 +500,25 @@ async function withdrawal(args, state) {
     return;
   }
 
-  let amount = parseInt(args.shift() || 1000, 10);
+  let percentStr = args.shift() || "100.0";
+  // pass: .1 0.1, 1, 1.0, 10, 10.0, 100, 100.0
+  // fail: 1000, 10.00
+  if (!/^1?\d?\d?(\.\d)?$/.test(percentStr)) {
+    console.error("Error: withdrawal percent must be between 0.1 and 100.0");
+    process.exit(1);
+  }
+  let percent = parseFloat(percentStr);
 
-  console.info("Initiating withdrawal...");
-  let paid = await CrowdNode.withdrawal(state.privKey, state.hotwallet, amount);
+  let permil = Math.round(percent * 10);
+  if (permil <= 0 || permil > 1000) {
+    console.error("Error: withdrawal percent must be between 0.1 and 100.0");
+    process.exit(1);
+  }
+
+  let realPercentStr = (permil / 10).toFixed(1);
+  console.info(`Initiating withdrawal of ${realPercentStr}...`);
+
+  let paid = await CrowdNode.withdrawal(state.privKey, state.hotwallet, permil);
   //let paidFloat = (paid.satoshis / DUFFS).toFixed(8);
   //let paidInt = paid.satoshis.toString().padStart(9, "0");
   console.info(`API Response: ${paid.api}`);
@@ -534,7 +562,7 @@ async function collectSignupFees(insightBaseUrl, pub) {
   showQr(pub);
 
   let signupTotalDash = (signupTotal / DUFFS).toFixed(8);
-  let signupMsg = `Please send >= ${signupTotal} (${signupTotalDash}) to Sign Up to CrowdNode`;
+  let signupMsg = `Please send >= ${signupTotal} (Đ${signupTotalDash}) to Sign Up to CrowdNode`;
   let msgPad = Math.ceil((qrWidth - signupMsg.length) / 2);
   let subMsg = "(plus whatever you'd like to deposit)";
   let subMsgPad = Math.ceil((qrWidth - subMsg.length) / 2);
@@ -551,13 +579,13 @@ async function collectSignupFees(insightBaseUrl, pub) {
   console.info(`Received ${payment.satoshis}`);
 }
 
-async function collectDeposit(insightBaseUrl, pub, amount) {
-  showQr(pub, amount);
+async function collectDeposit(insightBaseUrl, pub, duffAmount) {
+  showQr(pub, duffAmount);
 
   let depositMsg = `Please send what you wish to deposit to ${pub}`;
-  if (amount) {
-    let depositDash = (amount / DUFFS).toFixed(8);
-    depositMsg = `Please deposit ${amount} (${depositDash}) to ${pub}`;
+  if (duffAmount) {
+    let depositDash = toDash(duffAmount);
+    depositMsg = `Please deposit ${duffAmount} (Đ${depositDash}) to ${pub}`;
   }
 
   let msgPad = Math.ceil((qrWidth - depositMsg.length) / 2);
@@ -574,6 +602,10 @@ async function collectDeposit(insightBaseUrl, pub, amount) {
   console.info(`Received ${payment.satoshis}`);
 }
 
+function toDash(duffs) {
+  return (duffs / DUFFS).toFixed(8);
+}
+
 // Run
 
 main().catch(function (err) {
index fe488b4c7a5b5bcd39f7f979165f1927db9c32f5..d37e756fecb2816fad0d39bce726408595402c43 100644 (file)
@@ -12,14 +12,16 @@ their KYC-free Blockchain CLI.
 
 You must have [node.js](https://webinstall.dev/node) installed:
 
+### Mac & Linux
+
 ```bash
-# Mac, Linux
 curl https://webinstall.dev/node | bash
 export PATH="${HOME}/.local/opt/node:$PATH"
 ```
 
+### Windows
+
 ```pwsh
-# Windows
 curl.exe -A MS https://webinstall.dev/node | powershell
 PATH %USERPROFILE%\.local\opt\node;%PATH%
 ```
@@ -34,7 +36,7 @@ npm install --location=global crowdnode-cli@v1
 Or
 
 ```bash
-# Run without installing 
+# Run without installing
 npx crowdnode-cli@v1
 ```
 
@@ -71,9 +73,9 @@ You will be given these options whenever the existing balance is low.
    ```bash
    crowdnode accept ./privkey.wif
    ```
-3. Deposit your stake
+3. Deposit your stake (in Dash)
    ```bash
-   crowdnode deposit ./privkey.wif
+   crowdnode deposit ./privkey.wif 10.0
    ```
 
 ## All Commmands
@@ -84,13 +86,13 @@ Usage:
     crowdnode status ./privkey.wif
     crowdnode signup ./privkey.wif
     crowdnode accept ./privkey.wif
-    crowdnode deposit ./privkey.wif [amount] [--no-reserve]
-    crowdnode withdrawal ./privkey.wif <permil> # 1-1000 (1.0-100.0%)
+    crowdnode deposit ./privkey.wif [dash-amount] [--no-reserve]
+    crowdnode withdrawal ./privkey.wif <percent> # 1.0-100.0 (steps by 0.1)
 
 Helpful Extras:
     crowdnode generate [./privkey.wif]
     crowdnode balance ./privkey.wif
-    crowdnode transfer ./source.wif <key-file-or-pub-addr>
+    crowdnode transfer ./source.wif <key-file-or-pub-addr> [dash-amount]
 
 CrowdNode HTTP RPC:
     crowdnode http FundsOpen <addr>
@@ -108,12 +110,11 @@ CrowdNode HTTP RPC:
 
 ## Glossary
 
-| Term          | Description                                                   |
-| ------------- | ------------------------------------------------------------- |
-| addr          | your Dash address (Base58Check-encoded Pay-to-PubKey Address) |
-| amount        | the integer value of "Duffs" (Đ/100000000)                    |
-| permil        | 1/1000, 1‰, or 0.1% - between 1 and 1000 (0.1% to 100.0%)     |
-| ./privkey.wif | the file path to your staking key in WIF (Base58Check) format |
+| Term          | Description                                                          |
+| ------------- | -------------------------------------------------------------------- |
+| addr          | your Dash address (Base58Check-encoded Pay-to-PubKey Address)        |
+| ./privkey.wif | the file path to your staking key in WIF (Base58Check) format        |
+| signature     | generated with [dashmsg](https://webinstall.dev/dashmsg) or dash-cli |
 
 # JS API Documentation
 
index 977c022e1d8af2aea2859731168a2626ce4db37c..5c5401fd5f779d7d0ca23c5352b7b6f10fdae43e 100644 (file)
@@ -201,7 +201,7 @@ CrowdNode.accept = async function (wif, hotwallet) {
 /**
  * @param {String} wif
  * @param {String} hotwallet
- * @param {Number} amount
+ * @param {Number} amount - Duffs (1/100000000 Dash)
  */
 CrowdNode.deposit = async function (wif, hotwallet, amount) {
   // Send Request Message
@@ -229,7 +229,7 @@ CrowdNode.deposit = async function (wif, hotwallet, amount) {
 /**
  * @param {String} wif
  * @param {String} hotwallet
- * @param {Number} permil - 1/1000 (percent, but per thousand)
+ * @param {Number} permil - 1/1000 (1/10 of a percent) 500 permille = 50.0 percent
  */
 CrowdNode.withdrawal = async function (wif, hotwallet, permil) {
   let valid = permil > 0 && permil <= 1000;