From 7697f38263d6d5a4902ba27ee01c577b2f4f4999 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 24 Jan 2023 18:34:24 -0700 Subject: [PATCH 01/10] chore: rm bin/crowdnode.js.bak --- bin/crowdnode.js.bak | 2360 ------------------------------------------ 1 file changed, 2360 deletions(-) delete mode 100755 bin/crowdnode.js.bak diff --git a/bin/crowdnode.js.bak b/bin/crowdnode.js.bak deleted file mode 100755 index e59b950..0000000 --- a/bin/crowdnode.js.bak +++ /dev/null @@ -1,2360 +0,0 @@ -#!/usr/bin/env node -"use strict"; -/*jshint maxcomplexity:25 */ - -require("dotenv").config({ path: ".env" }); -require("dotenv").config({ path: ".env.secret" }); - -let HOME = process.env.HOME || ""; - -//@ts-ignore -let pkg = require("../package.json"); - -let Fs = require("fs").promises; -let Path = require("path"); - -let Cipher = require("./_cipher.js"); -let CrowdNode = require("../lib/crowdnode.js"); -let Dash = require("../lib/dash.js"); -let Insight = require("../lib/insight.js"); -let Prompt = require("./_prompt.js"); -let Qr = require("../lib/qr.js"); -let Ws = require("../lib/ws.js"); - -let Dashcore = require("@dashevo/dashcore-lib"); - -const DONE = "✅"; -const TODO = "ℹ️"; -const NO_SHADOW = "NONE"; -const DUFFS = 100000000; - -let shownDefault = false; -let qrWidth = 2 + 33 + 2; -// Sign Up Fees: -// 0.00236608 // required for signup -// 0.00002000 // TX fee estimate -// 0.00238608 // minimum recommended amount -// Target: -// 0.01000000 -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 paths = {}; -let configdir = `.config/crowdnode`; -let keysDir = Path.join(HOME, `${configdir}/keys`); -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(); -} - -function showHelp() { - showVersion(); - - console.info("Quick Start:"); - // technically this also has [--no-reserve] - console.info(" crowdnode stake [addr-or-import-key | --create-new]"); - - console.info(""); - console.info("Usage:"); - console.info(" crowdnode help"); - console.info(" crowdnode version"); - console.info(""); - console.info(" crowdnode status [keyfile-or-addr]"); - console.info(" crowdnode signup [keyfile-or-addr]"); - console.info(" crowdnode accept [keyfile-or-addr]"); - console.info( - " crowdnode deposit [keyfile-or-addr] [dash-amount] [--no-reserve]", - ); - console.info( - " crowdnode withdraw [keyfile-or-addr] # 1.0-100.0 (steps by 0.1)", - ); - console.info(""); - - console.info("Helpful Extras:"); - console.info(" crowdnode balance [keyfile-or-addr]"); // addr - console.info(" crowdnode load [keyfile-or-addr] [dash-amount]"); // addr - console.info( - " crowdnode transfer [dash-amount]", - ); // custom - console.info(""); - - console.info("Key Management & Encryption:"); - console.info(" crowdnode init"); - console.info(" crowdnode generate [--plain-text] [./privkey.wif]"); - console.info(" crowdnode encrypt"); // TODO allow encrypting one-by-one? - console.info(" crowdnode list"); - console.info(" crowdnode use "); - console.info(" crowdnode import "); - //console.info(" crowdnode import <(dash-cli dumpprivkey )"); // TODO - //console.info(" crowdnode export "); // TODO - console.info(" crowdnode passphrase # set or change passphrase"); - console.info(" crowdnode decrypt"); // TODO allow decrypting one-by-one? - console.info(" crowdnode delete "); - console.info(""); - - console.info("CrowdNode HTTP RPC:"); - console.info(" crowdnode http FundsOpen "); - console.info(" crowdnode http VotingOpen "); - console.info(" crowdnode http GetFunds "); - console.info(" crowdnode http GetFundsFrom "); - console.info(" crowdnode http GetBalance "); - console.info(" crowdnode http GetMessages "); - console.info(" crowdnode http IsAddressInUse "); - // TODO create signature rather than requiring it - console.info(" crowdnode http SetEmail ./privkey.wif "); - console.info(" crowdnode http Vote ./privkey.wif "); - console.info(" "); - console.info( - " crowdnode http SetReferral ./privkey.wif ", - ); - 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 = {}; - -async function main() { - /*jshint maxcomplexity:40 */ - /*jshint maxstatements:500 */ - - // Usage: - // crowdnode [flags] [options] - // Example: - // crowdnode withdraw ./Xxxxpubaddr.wif 100.0 - - let args = process.argv.slice(2); - - // flags - let forceGenerate = removeItem(args, "--create-new"); - let forceConfirm = removeItem(args, "--unconfirmed"); - let plainText = removeItem(args, "--plain-text"); - let noReserve = removeItem(args, "--no-reserve"); - - let subcommand = args.shift(); - - if (!subcommand || ["--help", "-h", "help"].includes(subcommand)) { - showHelp(); - process.exit(0); - return; - } - - if (["--version", "-V", "version"].includes(subcommand)) { - showVersion(); - process.exit(0); - return; - } - - // - // - // find addr by name or by file or by string - await Fs.mkdir(keysDir, { - recursive: true, - }); - - let defaultAddr = await Fs.readFile(defaultWifPath, "utf8").catch( - emptyStringOnErrEnoent, - ); - defaultAddr = defaultAddr.trim(); - - let insightBaseUrl = - process.env.INSIGHT_BASE_URL || "https://insight.dash.org"; - let insightApi = Insight.create({ baseUrl: insightBaseUrl }); - let dashApi = Dash.create({ insightApi: insightApi }); - - if ("stake" === subcommand) { - await stakeDash( - { - dashApi, - insightApi, - insightBaseUrl, - defaultAddr, - forceGenerate, - noReserve, - }, - args, - ); - process.exit(0); - return; - } - - if ("list" === subcommand) { - await listKeys({ dashApi, defaultAddr }, args); - process.exit(0); - return; - } - - if ("init" === subcommand) { - await initKeystore({ defaultAddr }); - process.exit(0); - return; - } - - if ("generate" === subcommand) { - await generateKey({ defaultKey: defaultAddr, plainText }, args); - process.exit(0); - return; - } - - if ("passphrase" === subcommand) { - await setPassphrase({}, args); - process.exit(0); - return; - } - - if ("import" === subcommand) { - let keypath = args.shift() || ""; - await importKey({ keypath }); - process.exit(0); - return; - } - - if ("encrypt" === subcommand) { - let addr = args.shift() || ""; - if (!addr) { - await encryptAll(null); - process.exit(0); - return; - } - - let keypath = await findWif(addr); - if (!keypath) { - console.error(`no managed key matches '${addr}'`); - process.exit(1); - return; - } - let key = await maybeReadKeyFileRaw(keypath); - if (!key) { - throw new Error("impossible error"); - } - await encryptAll([key]); - process.exit(0); - return; - } - - if ("decrypt" === subcommand) { - let addr = args.shift() || ""; - if (!addr) { - await decryptAll(null); - await Fs.writeFile(shadowPath, NO_SHADOW, "utf8").catch( - emptyStringOnErrEnoent, - ); - process.exit(0); - return; - } - let keypath = await findWif(addr); - if (!keypath) { - console.error(`no managed key matches '${addr}'`); - process.exit(1); - return; - } - let key = await maybeReadKeyFileRaw(keypath); - if (!key) { - throw new Error("impossible error"); - } - await decryptAll([key]); - process.exit(0); - return; - } - - // use or select or default... ? - if ("use" === subcommand) { - await setDefault(null, args); - process.exit(0); - return; - } - - // helper for debugging - if ("transfer" === subcommand) { - await transferBalance( - { dashApi, defaultAddr, forceConfirm, insightBaseUrl, insightApi }, - args, - ); - process.exit(0); - return; - } - - let rpc = ""; - if ("http" === subcommand) { - rpc = args.shift() || ""; - if (!rpc) { - showHelp(); - process.exit(1); - return; - } - - let [addr] = await mustGetAddr({ defaultAddr }, args); - - await initCrowdNode(insightBaseUrl); - // ex: http (, ...) - args.unshift(addr); - let hasRpc = rpc in CrowdNode.http; - if (!hasRpc) { - console.error(`Unrecognized rpc command ${rpc}`); - console.error(); - showHelp(); - process.exit(1); - } - //@ts-ignore - TODO use `switch` or make Record Type - let result = await CrowdNode.http[rpc].apply(null, args); - console.info(``); - console.info(`${rpc} ${addr}:`); - if ("string" === typeof result) { - console.info(result); - } else { - console.info(JSON.stringify(result, null, 2)); - } - process.exit(0); - return; - } - - if ("load" === subcommand) { - await loadAddr({ defaultAddr, insightBaseUrl }, args); - process.exit(0); - return; - } - - // keeping rm for backwards compat - if ("rm" === subcommand || "delete" === subcommand) { - await initCrowdNode(insightBaseUrl); - let [addr, filepath] = await mustGetAddr({ defaultAddr }, args); - await removeKey({ addr, dashApi, filepath, insightBaseUrl }, args); - process.exit(0); - return; - } - - if ("balance" === subcommand) { - if (args.length) { - await getBalance({ dashApi, defaultAddr }, args); - process.exit(0); - return; - } - - await getAllBalances({ dashApi, defaultAddr }, args); - process.exit(0); - return; - } - - if ("status" === subcommand) { - await getStatus({ dashApi, defaultAddr, insightBaseUrl }, args); - process.exit(0); - return; - } - - if ("signup" === subcommand) { - await sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args); - process.exit(0); - return; - } - - if ("accept" === subcommand) { - await acceptTerms({ dashApi, defaultAddr, insightBaseUrl }, args); - process.exit(0); - return; - } - - if ("deposit" === subcommand) { - await depositDash( - { dashApi, defaultAddr, insightBaseUrl, noReserve }, - args, - ); - process.exit(0); - return; - } - - // The misspelling 'withdrawal' is kept as part of compatibility < v1.7 - if ("withdrawal" === subcommand) { - console.warn( - `[Deprecation Notice] 'crowdnode withdrawal' is a misspelling of 'crowdnode withdraw'`, - ); - subcommand = "withdraw"; - } - - if ("withdraw" === subcommand) { - await withdrawDash({ dashApi, defaultAddr, insightBaseUrl }, args); - process.exit(0); - return; - } - - console.error(`Unrecognized subcommand ${subcommand}`); - console.error(); - showHelp(); - process.exit(1); -} - -/** - * @param {String} insightBaseUrl - * @param {String} addr - */ -async function collectSignupFees(insightBaseUrl, addr) { - console.info(``); - showQr(addr); - - let signupTotalDash = toDash(signupTotal); - 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); - - console.info(); - console.info(" ".repeat(msgPad) + signupMsg); - console.info(" ".repeat(subMsgPad) + subMsg); - console.info(); - - console.info(""); - console.info("(waiting...)"); - console.info(""); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); - console.info(`Received ${payment.satoshis}`); -} - -/** - * @param {String} insightBaseUrl - * @param {String} addr - * @param {Number} duffAmount - */ -async function collectDeposit(insightBaseUrl, addr, duffAmount) { - console.info(``); - showQr(addr, duffAmount); - - let depositMsg = `Please send what you wish to deposit to ${addr}`; - if (duffAmount) { - let dashAmount = toDash(duffAmount); - depositMsg = `Please deposit ${duffAmount} (Đ${dashAmount}) to ${addr}`; - } - - let msgPad = Math.ceil((qrWidth - depositMsg.length) / 2); - msgPad = Math.max(0, msgPad); - - console.info(); - console.info(" ".repeat(msgPad) + depositMsg); - console.info(); - - console.info(""); - console.info("(waiting...)"); - console.info(""); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); - console.info(`Received ${payment.satoshis}`); -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function getStatus({ dashApi, defaultAddr, insightBaseUrl }, args) { - let [addr] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - - console.info(); - console.info(`API Actions Complete for ${addr}:`); - console.info(` ${state.signup} SignUpForApi`); - console.info(` ${state.accept} AcceptTerms`); - console.info(` ${state.deposit} DepositReceived`); - console.info(); - let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); - // may be unregistered / undefined - /* - * { - * '@odata.context': 'https://app.crowdnode.io/odata/$metadata#Edm.String', - * value: 'Address not found.' - * } - */ - if (!crowdNodeBalance.TotalBalance) { - crowdNodeBalance.TotalBalance = 0; - } - let crowdNodeDuff = toDuff(crowdNodeBalance.TotalBalance); - console.info( - `CrowdNode Stake: ${crowdNodeDuff} (Đ${crowdNodeBalance.TotalBalance})`, - ); - console.info(); - return; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args) { - let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - let balanceInfo = await checkBalance({ addr, dashApi }); - - if (state.status?.signup) { - console.info(`${addr} is already signed up. Here's the account status:`); - console.info(` ${state.signup} SignUpForApi`); - console.info(` ${state.accept} AcceptTerms`); - console.info(` ${state.deposit} DepositReceived`); - return; - } - - let hasEnough = balanceInfo.balanceSat > signupOnly + feeEstimate; - if (!hasEnough) { - await collectSignupFees(insightBaseUrl, addr); - } - - let wif = await maybeReadKeyPaths(name, { wif: true }); - - console.info("Requesting account..."); - await CrowdNode.signup(wif, hotwallet); - state.signup = DONE; - console.info(` ${state.signup} SignUpForApi`); - return; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function acceptTerms({ dashApi, defaultAddr, insightBaseUrl }, args) { - let [addr, name] = await mustGetAddr({ defaultAddr }, args); - - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - let balanceInfo = await dashApi.getInstantBalance(addr); - - if (!state.status?.signup) { - console.info(`${addr} is not signed up yet. Here's the account status:`); - console.info(` ${state.signup} SignUpForApi`); - console.info(` ${state.accept} AcceptTerms`); - process.exit(1); - return; - } - - if (state.status?.accept) { - console.info(`${addr} is already signed up. Here's the account status:`); - console.info(` ${state.signup} SignUpForApi`); - console.info(` ${state.accept} AcceptTerms`); - console.info(` ${state.deposit} DepositReceived`); - return; - } - let hasEnough = balanceInfo.balanceSat > acceptOnly + feeEstimate; - if (!hasEnough) { - await collectSignupFees(insightBaseUrl, addr); - } - - let wif = await maybeReadKeyPaths(name, { wif: true }); - - console.info("Accepting terms..."); - await CrowdNode.accept(wif, hotwallet); - state.accept = DONE; - console.info(` ${state.accept} AcceptTerms`); - return; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Boolean} opts.noReserve - * @param {Array} args - */ -async function depositDash( - { dashApi, defaultAddr, insightBaseUrl, noReserve }, - args, -) { - let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - let balanceInfo = await dashApi.getInstantBalance(addr); - - if (!state.status?.accept) { - console.error(`no account for address ${addr}`); - process.exit(1); - return; - } - - // this would allow for at least 2 withdrawals costing (21000 + 1000) - let reserve = 50000; - let reserveDash = toDash(reserve); - if (!noReserve) { - console.info( - `reserving ${reserve} (Đ${reserveDash}) for withdrawals (--no-reserve to disable)`, - ); - } else { - reserve = 0; - } - - // TODO if unconfirmed, check utxos instead - - // deposit what the user asks, or all that we have, - // or all that the user deposits - but at least 2x the reserve - let desiredAmountDash = parseFloat(args.shift() || "0"); - let desiredAmountDuff = Math.round(desiredAmountDash * DUFFS); - let effectiveAmount = desiredAmountDuff; - if (!effectiveAmount) { - effectiveAmount = balanceInfo.balanceSat - reserve; - } - let needed = Math.max(reserve * 2, effectiveAmount + reserve); - - if (balanceInfo.balanceSat < needed) { - let ask = 0; - if (desiredAmountDuff) { - ask = desiredAmountDuff + reserve + -balanceInfo.balanceSat; - } - await collectDeposit(insightBaseUrl, addr, ask); - balanceInfo = await dashApi.getInstantBalance(addr); - if (balanceInfo.balanceSat < needed) { - let balanceDash = toDash(balanceInfo.balanceSat); - console.error( - `Balance is still too small: ${balanceInfo.balanceSat} (Đ${balanceDash})`, - ); - process.exit(1); - return; - } - } - if (!desiredAmountDuff) { - effectiveAmount = balanceInfo.balanceSat - reserve; - } - - let effectiveDash = toDash(effectiveAmount); - console.info( - `Initiating deposit of ${effectiveAmount} (Đ${effectiveDash})...`, - ); - - let wif = await maybeReadKeyPaths(name, { wif: true }); - - await CrowdNode.deposit(wif, hotwallet, effectiveAmount); - state.deposit = DONE; - console.info(` ${state.deposit} DepositReceived`); - return; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {Boolean} opts.forceGenerate - * @param {String} opts.insightBaseUrl - * @param {any} opts.insightApi - * @param {Boolean} opts.noReserve - * @param {Array} args - */ -async function stakeDash( - { - dashApi, - defaultAddr, - forceGenerate, - insightApi, - insightBaseUrl, - noReserve, - }, - args, -) { - let err = await Fs.access(args[0]).catch(Object); - let addr; - if (!err) { - let keypath = args.shift() || ""; - addr = await importKey({ keypath }); - } else if (forceGenerate) { - addr = await generateKey({ defaultKey: defaultAddr }, []); - } else { - addr = await initKeystore({ defaultAddr }); - } - - if (!addr) { - let [_addr] = await mustGetAddr({ defaultAddr }, args); - addr = _addr; - } - - let extra = feeEstimate; - console.info("Checking CrowdNode account... "); - await CrowdNode.init({ - baseUrl: "https://app.crowdnode.io", - insightBaseUrl, - }); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - - if (!state.status?.accept) { - if (!state.status?.signup) { - let signUpDeposit = signupOnly + feeEstimate; - console.info( - ` ${TODO} SignUpForApi deposit is ${signupOnly} (+ tx fee)`, - ); - extra += signUpDeposit; - } else { - console.info(` ${DONE} SignUpForApi complete`); - } - let acceptDeposit = acceptOnly + feeEstimate; - console.info(` ${TODO} AcceptTerms deposit is ${acceptOnly} (+ tx fee)`); - extra += acceptDeposit; - } - - let desiredAmountDash = args.shift() || "0.5"; - let effectiveDuff = toDuff(desiredAmountDash); - effectiveDuff += extra; - - let balanceInfo = await dashApi.getInstantBalance(addr); - effectiveDuff -= balanceInfo.balanceSat; - - if (effectiveDuff > 0) { - effectiveDuff = roundDuff(effectiveDuff, 3); - let effectiveDash = toDash(effectiveDuff); - await plainLoadAddr({ - addr, - effectiveDash, - effectiveDuff, - insightBaseUrl, - }); - } - - if (!state.status?.accept) { - if (!state.status?.signup) { - await sendSignup({ dashApi, defaultAddr: addr, insightBaseUrl }, [addr]); - } - await acceptTerms({ dashApi, defaultAddr: addr, insightBaseUrl }, [addr]); - } - - await depositDash( - { dashApi, defaultAddr: addr, insightBaseUrl, noReserve }, - [addr].concat(args), - ); - - await checkBalance({ addr, dashApi }); -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function withdrawalDash({ dashApi, defaultAddr, insightBaseUrl }, args) { - let [addr] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - - if (!state.status?.accept) { - console.error(`no account for address ${addr}`); - process.exit(1); - return; - } - - 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); - - 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 wifname = await findWif(addr); - let filepath = Path.join(keysDir, wifname); - let wif = await maybeReadKeyFile(filepath); - let paid = await CrowdNode.withdrawal(wif, hotwallet, permil); - //let paidFloat = (paid.satoshis / DUFFS).toFixed(8); - //let paidInt = paid.satoshis.toString().padStart(9, "0"); - console.info(`API Response: ${paid.api}`); - return; -} - -/** - * @param {String} insightBaseUrl - */ -async function initCrowdNode(insightBaseUrl) { - if (CrowdNode.main.hotwallet) { - return; - } - process.stdout.write("Checking CrowdNode API... "); - await CrowdNode.init({ - baseUrl: "https://app.crowdnode.io", - insightBaseUrl, - }); - console.info(`(hotwallet ${CrowdNode.main.hotwallet})`); -} - -/** - * @param {Object} opts - * @param {String} opts.addr - * @param {String} opts.hotwallet - */ -async function getCrowdNodeStatus({ addr, hotwallet }) { - let state = { - signup: TODO, - accept: TODO, - deposit: TODO, - status: { - signup: 0, - accept: 0, - deposit: 0, - }, - }; - - //@ts-ignore - TODO why warnings? - let status = await CrowdNode.status(addr, hotwallet); - if (status) { - state.status = status; - } - if (state.status?.signup) { - state.signup = DONE; - } - if (state.status?.accept) { - state.accept = DONE; - } - if (state.status?.deposit) { - state.deposit = DONE; - } - return state; -} - -/** - * @param {Null} _ - * @param {Array} args - */ -async function setDefault(_, args) { - let addr = args.shift() || ""; - - let keyname = await findWif(addr); - if (!keyname) { - console.error(`no key matches '${addr}'`); - process.exit(1); - return; - } - - let filepath = Path.join(keysDir, keyname); - let wif = await maybeReadKeyFile(filepath); - let pk = new Dashcore.PrivateKey(wif); - let pub = pk.toAddress().toString(); - - console.info("set", defaultWifPath, pub); - await Fs.writeFile(defaultWifPath, pub, "utf8"); -} - -/** - * @param {String} defaultAddr - * @param {Object} [opts] - * @param {Boolean} opts.wif - */ -async function mustGetDefaultWif(defaultAddr, opts) { - let defaultWif = ""; - if (defaultAddr) { - let keyfile = Path.join(keysDir, `${defaultAddr}.wif`); - let raw = await maybeReadKeyFileRaw(keyfile, opts); - // misnomering wif here a bit - defaultWif = raw?.wif || raw?.addr || ""; - } - if (defaultWif && !shownDefault) { - shownDefault = true; - debug(`Selected default staking key ${defaultAddr}`); - return defaultWif; - } - - console.error(); - console.error(`Error: no default staking key selected.`); - console.error(); - console.error(`Select a different address:`); - console.error(` crowdnode list`); - console.error(` crowdnode use `); - console.error(``); - console.error(`Or create a new staking key:`); - console.error(` crowdnode generate`); - console.error(); - process.exit(1); - return ""; -} - - - -/** - * @param {String} addr - Base58Check pubKeyHash address - * @param {Number} duffs - 1/100000000 of a DASH - */ -function showQr(addr, duffs = 0) { - let dashAmount = toDash(duffs); - let dashUri = `dash://${addr}`; - if (duffs) { - dashUri += `?amount=${dashAmount}`; - } - - let dashQr = Qr.ascii(dashUri, { indent: 4, size: "mini" }); - let addrPad = Math.max(0, Math.ceil((qrWidth - dashUri.length) / 2)); - - console.info(dashQr); - console.info(); - console.info(" ".repeat(addrPad) + dashUri); -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {Array} 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)`); - } - 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; - } - - /* - let pk = new Dashcore.PrivateKey(wif); - let pub = pk.toAddress().toString(); - if (`${pub}.wif` !== wifname) { - // sanity check - warns.push({ - node: wifname, - error: new Error( - `computed pubkey '${pub}' of WIF does not match filename '${wifname}'`, - ), - }); - return; - } - */ - - process.stdout.write(`| ${addr} |`); - - let balanceInfo = await dashApi.getInstantBalance(addr); - let balanceDASH = toDASH(balanceInfo.balanceSat); - - let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); - if (!crowdNodeBalance.TotalBalance) { - crowdNodeBalance.TotalBalance = 0; - crowdNodeBalance.TotalDividend = 0; - } - let crowdNodeDuffNum = toDuff(crowdNodeBalance.TotalBalance); - let crowdNodeDASH = toDASH(crowdNodeDuffNum); - - let crowdNodeDivNum = toDuff(crowdNodeBalance.TotalDividend); - let crowdNodeDivDASH = toDASH(crowdNodeDivNum); - process.stdout.write( - ` ${balanceDASH} | ${crowdNodeDASH} | ${crowdNodeDivDASH} |`, - ); - - totals.key += balanceInfo.balanceSat; - totals.dividend += crowdNodeBalance.TotalDividend; - totals.stake += crowdNodeBalance.TotalBalance; - - console.info(); - }, Promise.resolve()); - 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:`); - warns.forEach(function (warn) { - console.warn(`${warn.node}: ${warn.error.message}`); - }); - console.warn(``); - } -} - - - -/** - * @param {Object} opts - * @param {String} opts.defaultAddr - * @param {any} opts.dashApi - TODO - * @param {Array} args - */ -async function getBalance({ dashApi, defaultAddr }, args) { - let [addr] = await mustGetAddr({ defaultAddr }, args); - await checkBalance({ addr, dashApi }); - //let balanceInfo = await checkBalance({ addr, dashApi }); - //console.info(balanceInfo); - return; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {Boolean} opts.forceConfirm - * @param {String} opts.insightBaseUrl - * @param {any} opts.insightApi - * @param {Array} args - */ -// ex: node ./bin/crowdnode.js transfer ./priv.wif 'pub' 0.01 -async function transferBalance( - { dashApi, defaultAddr, forceConfirm, insightBaseUrl, insightApi }, - args, -) { - let wif = await mustGetWif({ defaultAddr }, args); - - let keyname = args.shift() || ""; - let newAddr = await wifFileToAddr(keyname); - let dashAmount = parseFloat(args.shift() || "0"); - let duffAmount = Math.round(dashAmount * DUFFS); - let tx; - if (duffAmount) { - tx = await dashApi.createPayment(wif, newAddr, duffAmount); - } else { - tx = await dashApi.createBalanceTransfer(wif, newAddr); - } - if (duffAmount) { - let dashAmountStr = toDash(duffAmount); - console.info( - `Transferring ${duffAmount} (Đ${dashAmountStr}) to ${newAddr}...`, - ); - } else { - console.info(`Transferring balance to ${newAddr}...`); - } - await insightApi.instantSend(tx); - console.info(`Queued...`); - setTimeout(function () { - // TODO take a cleaner approach - // (waitForVout needs a reasonable timeout) - console.error(`Error: Transfer did not complete.`); - if (forceConfirm) { - console.error(`(using --unconfirmed may lead to rejected double spends)`); - } - process.exit(1); - }, 30 * 1000); - await Ws.waitForVout(insightBaseUrl, newAddr, 0); - console.info(`Accepted!`); - return; -} - -<<<<<<< Updated upstream - // State 1: not initialized, what does the user want? - if (needsInit) { - for (;;) { - let no; - if (!_force) { - no = await Prompt.prompt( - "Would you like to encrypt your keys with a passphrase? [Y/n]: ", - ); - } -======= -/** - * @param {Object} opts - * @param {String} opts.addr - * @param {any} opts.dashApi - TODO - */ -async function checkBalance({ addr, dashApi }) { - // deposit if balance is over 100,000 (0.00100000) - console.info("Checking balance... "); - let balanceInfo = await dashApi.getInstantBalance(addr); - let balanceDASH = toDASH(balanceInfo.balanceSat); ->>>>>>> Stashed changes - - let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); - if (!crowdNodeBalance.TotalBalance) { - crowdNodeBalance.TotalBalance = 0; - crowdNodeBalance.TotalDividend = 0; - } - - let crowdNodeDuffNum = toDuff(crowdNodeBalance.TotalBalance); - let crowdNodeDASH = toDASH(crowdNodeDuffNum); - - let crowdNodeDivNum = toDuff(crowdNodeBalance.TotalDividend); - let crowdNodeDASHDiv = toDASH(crowdNodeDivNum); - - console.info(`Key: ${balanceDASH}`); - console.info(`CrowdNode: ${crowdNodeDASH}`); - console.info(`Dividends: ${crowdNodeDASHDiv}`); - console.info(); - /* - let balanceInfo = await insightApi.getBalance(pub); - if (balanceInfo.unconfirmedBalanceSat || balanceInfo.unconfirmedAppearances) { - if (!forceConfirm) { - console.error( - `Error: This address has pending transactions. Please try again in 1-2 minutes or use --unconfirmed.`, - ); - console.error(balanceInfo); - if ("status" !== subcommand) { - process.exit(1); - return; - } - } - } - */ - return balanceInfo; -} - -/** - * @param {Object} opts - * @param {String} opts.defaultAddr - * @param {Array} args - * @returns {Promise<[String, String]>} - */ -async function mustGetAddr({ defaultAddr }, args) { - let name = args.shift() ?? ""; - if (34 === name.length) { - // looks like addr already - // TODO make function for addr-lookin' check - return [name, name]; - } - - let addr = await maybeReadKeyPaths(name, { wif: false }); - if (addr) { - if (34 === addr.length) { - return [addr, name]; - } - //let pk = new Dashcore.PrivateKey(wif); - //let addr = pk.toAddress().toString(); - return [addr, name]; - } - - let isNum = !isNaN(parseFloat(name)); - if (isNum) { - args.unshift(name); - name = ""; - } - - if (name) { - console.error(); - console.error(`could not read '${name}' in ./ or match in ${keysDirRel}/.`); - console.error(); - process.exit(1); - return ["", name]; - } - - addr = await mustGetDefaultWif(defaultAddr, { wif: false }); - - // TODO we don't need defaultAddr, right? because it could be old? - return [addr, addr]; -} - -/** - * @param {Object} opts - * @param {String} opts.defaultAddr - * @param {Array} args - */ -async function mustGetWif({ defaultAddr }, args) { - let name = args.shift() ?? ""; - - let wif = await maybeReadKeyPaths(name, { wif: true }); - if (wif) { - return wif; - } - - let isNum = !isNaN(parseFloat(name)); - if (isNum) { - args.unshift(name); - name = ""; - } - - if (name) { - console.error(); - console.error( - `'${name}' does not match a staking key in ./ or ${keysDirRel}/`, - ); - console.error(); - process.exit(1); - return ""; - } - - wif = await mustGetDefaultWif(defaultAddr); - - return wif; -} - -/** - * @param {Object} opts - * @param {String} opts.defaultAddr - */ -async function initKeystore({ defaultAddr }) { - // if we have no keys, make one - let wifnames = await listManagedKeynames(); - if (!wifnames.length) { - return await generateKey({ defaultKey: defaultAddr }, []); - } - // if we have no passphrase, ask about it - await initPassphrase(); - return defaultAddr || wifnames[0]; -} - -/** - * @param {String} name - * @param {Object} opts - * @param {Boolean} opts.wif - * @returns {Promise} - wif - */ -async function maybeReadKeyPaths(name, opts) { - let privKey = ""; - - // prefix match in .../keys/ - let wifname = await findWif(name); - if (!wifname) { - return ""; - } - - if (false === opts.wif) { - return wifname.slice(0, -".wif".length); - } - - let filepath = Path.join(keysDir, wifname); - privKey = await maybeReadKeyFile(filepath); - if (!privKey) { - // local in ./ - privKey = await maybeReadKeyFile(name); - } - - return privKey; -} - -// Subcommands - -/** - * @param {Object} psuedoState - * @param {String} psuedoState.defaultKey - addr name of default key - * @param {Boolean} [psuedoState.plainText] - don't encrypt - * @param {Array} args - */ -async function generateKey({ defaultKey, plainText }, args) { - let name = args.shift(); - //@ts-ignore - TODO submit JSDoc PR for Dashcore - let pk = new Dashcore.PrivateKey(); - - let addr = pk.toAddress().toString(); - let plainWif = pk.toWIF(); - - let wif = plainWif; - if (!plainText) { - wif = await maybeEncrypt(plainWif); - } - - let filename = `~/${configdir}/keys/${addr}.wif`; - let filepath = Path.join(`${keysDir}/${addr}.wif`); - let note = ""; - if (name) { - filename = name; - filepath = name; - note = `\n(for pubkey address ${addr})`; - let err = await Fs.access(filepath).catch(Object); - if (!err) { - // TODO - console.info(`'${filepath}' already exists (will not overwrite)`); - process.exit(0); - return; - } - } - - await Fs.writeFile(filepath, wif, "utf8"); - if (!name && !defaultKey) { - await Fs.writeFile(defaultWifPath, addr, "utf8"); - } - - console.info(``); - console.info(`Generated ${filename} ${note}`); - console.info(``); - return addr; -} - -async function initPassphrase() { - let needsInit = false; - let shadow = await Fs.readFile(shadowPath, "utf8").catch( - emptyStringOnErrEnoent, - ); - if (!shadow) { - needsInit = true; - } - if (needsInit) { - await cmds.getPassphrase({}, []); - } -} - -/** - * @param {Object} state - * @param {Boolean} [state._askPreviousPassphrase] - don't ask for passphrase again - * @param {Array} args - */ -async function setPassphrase({ _askPreviousPassphrase }, args) { - let result = { - passphrase: "", - changed: false, - }; - let date = getFsDateString(); - - // get the old passphrase - if (false !== _askPreviousPassphrase) { - // TODO should contain the shadow? - await cmds.getPassphrase({ _rotatePassphrase: true }, []); - } - - // get the new passphrase - let newPassphrase = await promptPassphrase(); - let curShadow = await Fs.readFile(shadowPath, "utf8").catch( - emptyStringOnErrEnoent, - ); - - let newShadow = await Cipher.shadowPassphrase(newPassphrase); - await Fs.writeFile(shadowPath, newShadow, "utf8"); - - let rawKeys = await readAllKeys(); - let encAddrs = rawKeys - .map(function (raw) { - if (raw.encrypted) { - return raw.addr; - } - }) - .filter(Boolean); - - // backup all currently encrypted files - //@ts-ignore - if (encAddrs.length) { - let filepath = Path.join(HOME, `${configdir}/keys.${date}.bak`); - console.info(``); - console.info(`Backing up previous (encrypted) keys:`); - encAddrs.unshift(`SHADOW:${curShadow}`); - await Fs.writeFile(filepath, encAddrs.join("\n") + "\n", "utf8"); - console.info(` ~/${configdir}/keys.${date}.bak`); - console.info(``); - } - cmds._setPassphrase(newPassphrase); - - await encryptAll(rawKeys, { rotateKey: true }); - - result.passphrase = newPassphrase; - result.changed = true; - return result; -} - -async function promptPassphrase() { - let newPassphrase; - for (;;) { - newPassphrase = await Prompt.prompt("Enter (new) passphrase: ", { - mask: true, - }); - newPassphrase = newPassphrase.trim(); - - let _newPassphrase = await Prompt.prompt("Enter passphrase again: ", { - mask: true, - }); - _newPassphrase = _newPassphrase.trim(); - - let match = Cipher.secureCompare(newPassphrase, _newPassphrase); - if (match) { - break; - } - - console.error("passphrases do not match"); - } - return newPassphrase; -} - -/** - * Import and Encrypt - * @param {Object} opts - * @param {String} opts.keypath - */ -async function importKey({ keypath }) { - let key = await maybeReadKeyFileRaw(keypath); - if (!key?.wif) { - console.error(`no key found for '${keypath}'`); - process.exit(1); - return; - } - - let encWif = await maybeEncrypt(key.wif); - let icon = "💾"; - if (encWif.includes(":")) { - icon = "🔐"; - } - let date = getFsDateString(); - - await safeSave( - Path.join(keysDir, `${key.addr}.wif`), - encWif, - Path.join(keysDir, `${key.addr}.${date}.bak`), - ); - - console.info(`${icon} Imported ${keysDirRel}/${key.addr}.wif`); - console.info(``); - - return key.addr; -} - -/** - * @param {Object} opts - * @param {Boolean} [opts._rotatePassphrase] - * @param {Boolean} [opts._force] - * @param {Array} args - */ -cmds.getPassphrase = async function ({ _rotatePassphrase, _force }, args) { - let result = { - passphrase: "", - changed: false, - }; - /* - if (!_rotatePassphrase) { - let cachedphrase = cmds._getPassphrase(); - if (cachedphrase) { - return cachedphrase; - } - } - */ - - // Three possible states: - // 1. no shadow file yet (ask to set one) - // 2. empty shadow file (initialized, but not set - don't ask to set one) - // 3. encrypted shadow file (initialized, requires passphrase) - let needsInit = false; - let shadow = await Fs.readFile(shadowPath, "utf8").catch( - emptyStringOnErrEnoent, - ); - if (!shadow) { - needsInit = true; - } else if (NO_SHADOW === shadow && _force) { - needsInit = true; - } - - // State 1: not initialized, what does the user want? - if (needsInit) { - for (;;) { - let no; - if (!_force) { - no = await Prompt.prompt( - "Would you like to set an encryption passphrase? [Y/n]: ", - ); - } - - // Set a passphrase and create shadow file - if (!no || ["yes", "y"].includes(no.toLowerCase())) { - result = await setPassphrase({ _askPreviousPassphrase: false }, args); - cmds._setPassphrase(result.passphrase); - return result; - } - - // ask user again - if (!["no", "n"].includes(no.toLowerCase())) { - continue; - } - - // No passphrase, create a NONE shadow file - await Fs.writeFile(shadowPath, NO_SHADOW, "utf8"); - return result; - } -<<<<<<< Updated upstream - let crowdNodeDuffNum = toDuff(crowdNodeBalance.TotalBalance); - let crowdNodeDASH = toDASH(crowdNodeDuffNum); - - let crowdNodeDivNum = toDuff(crowdNodeBalance.TotalDividend); - let crowdNodeDivDASH = toDASH(crowdNodeDivNum); - process.stdout.write( - ` ${balanceDASH} | ${crowdNodeDASH} | ${crowdNodeDivDASH} |`, - ); - - totals.key += balanceInfo.balanceSat; - totals.dividend += crowdNodeBalance.TotalDividend; - totals.stake += crowdNodeBalance.TotalBalance; - - console.info(); - }, Promise.resolve()); - 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.keyDash} | ${totals.stakeDash} | ${totals.dividendDash} |`, - ); - debug(``); - - if (warns.length) { - console.warn(`Warnings:`); - warns.forEach(function (warn) { - console.warn(`${warn.node}: ${warn.error.message}`); - }); - console.warn(``); - } -} - -/** - * @param {String} name - ex: Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.wif.enc - */ -function isNamedLikeKey(name) { - // TODO distinguish with .enc extension? - let hasGoodLength = 34 + 4 === name.length || 34 + 4 + 4 === name.length; - let knownExt = name.endsWith(".wif") || name.endsWith(".wif.enc"); - let isTmp = name.startsWith(".") || name.startsWith("_"); - return hasGoodLength && knownExt && !isTmp; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.addr - * @param {String} opts.filepath - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function removeKey({ addr, dashApi, filepath, insightBaseUrl }, args) { - let balanceInfo = await dashApi.getInstantBalance(addr); - - let balanceDash = toDash(balanceInfo.balanceSat); - if (balanceInfo.balanceSat) { - console.error(``); - console.error(`Error: ${addr}`); - console.error( - ` still has a balance of ${balanceInfo.balanceSat} (Đ${balanceDash})`, - ); - console.error(` (transfer to another address before deleting)`); - console.error(``); - process.exit(1); - return; - } - - await initCrowdNode(insightBaseUrl); - let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); - if (!crowdNodeBalance) { - // may be janky if not registered - crowdNodeBalance = {}; - } - if (!crowdNodeBalance.TotalBalance) { - crowdNodeBalance.TotalBalance = 0; - } - let crowdNodeDash = toDash(crowdNodeBalance.TotalBalance); - if (crowdNodeBalance.TotalBalance) { - console.error(``); - console.error(`Error: ${addr}`); - console.error( - ` still staking ${crowdNodeBalance.TotalBalance} (Đ${crowdNodeDash}) on CrowdNode`, - ); - console.error( - ` (withdraw 100.0 and transfer to another address before deleting)`, - ); - console.error(``); - process.exit(1); - return; - } - - let wifname = await findWif(addr); - let fullpath = Path.join(keysDir, wifname); - let wif = await maybeReadKeyPaths(filepath, { wif: true }); - - await Fs.unlink(fullpath).catch(function (err) { - console.error(`could not remove ${filepath}: ${err.message}`); - process.exit(1); - }); - - let wifnames = await listManagedKeynames(); - console.info(``); - console.info(`No balances found. Removing ${filepath}.`); - console.info(``); - console.info(`Backup (just in case):`); - console.info(` ${wif}`); - console.info(``); - if (!wifnames.length) { - console.info(`No keys left.`); - console.info(``); - } else { - let newAddr = wifnames[0]; - debug(`Selected ${newAddr} as new default staking key.`); - await Fs.writeFile(defaultWifPath, addr.replace(".wif", ""), "utf8"); - console.info(``); -======= ->>>>>>> Stashed changes - } - - // State 2: shadow already initialized to empty - // (user doesn't want a passphrase) - if (!shadow) { - cmds._setPassphrase(""); - return result; - } - - // State 3: passphrase & shadow already in use - for (;;) { - let prompt = `Enter passphrase: `; - if (_rotatePassphrase) { - prompt = `Enter (current) passphrase: `; - } - result.passphrase = await Prompt.prompt(prompt, { - mask: true, - }); - result.passphrase = result.passphrase.trim(); - if (!result.passphrase || "q" === result.passphrase) { - console.error("cancel: no passphrase"); - process.exit(1); - return result; - } - - let match = await Cipher.checkPassphrase(result.passphrase, shadow); - if (match) { - cmds._setPassphrase(result.passphrase); - console.info(``); - return result; - } - - console.error("incorrect passphrase"); - } - - throw new Error("SANITY FAIL: unreachable return"); -}; - -cmds._getPassphrase = function () { - return ""; -}; - -/** - * @param {String} passphrase - */ -cmds._setPassphrase = function (passphrase) { - // Look Ma! A private variable! - cmds._getPassphrase = function () { - return passphrase; - }; -}; - -/** - * Encrypt ALL-the-things! - * @param {Object} [opts] - * @param {Boolean} opts.rotateKey - * @param {Array?} rawKeys - */ -async function encryptAll(rawKeys, opts) { - if (!rawKeys) { - rawKeys = await readAllKeys(); - } - let date = getFsDateString(); - - let passphrase = cmds._getPassphrase(); - if (!passphrase) { - let result = await cmds.getPassphrase({ _force: true }, []); - if (result.changed) { - // encryptAll was already called on rotation - return; - } - passphrase = result.passphrase; - } - - console.info(`Encrypting...`); - console.info(``); - await rawKeys.reduce(async function (promise, key) { - await promise; - - if (key.encrypted && !opts?.rotateKey) { - console.info(`🙈 ${key.addr} [already encrypted]`); - return; - } - let encWif = await maybeEncrypt(key.wif, { force: true }); - await safeSave( - Path.join(keysDir, `${key.addr}.wif`), - encWif, - Path.join(keysDir, `${key.addr}.${date}.bak`), - ); - console.info(`🔑 ${key.addr}`); - }, Promise.resolve()); - console.info(``); - console.info(`Done 🔐`); - console.info(``); -} - -/** - * @param {String} encWif - */ -async function decrypt(encWif) { - let passphrase = cmds._getPassphrase(); - if (!passphrase) { - let result = await cmds.getPassphrase({}, []); - passphrase = result.passphrase; - // we don't return just in case they're setting a passphrase to - // decrypt a previously encrypted file (i.e. for recovery from elsewhere) - } - let key128 = await Cipher.deriveKey(passphrase); - let cipher = Cipher.create(key128); - - return cipher.decrypt(encWif); -} - -/** - * Decrypt ALL-the-things! - * @param {Array?} rawKeys - */ -async function decryptAll(rawKeys) { - if (!rawKeys) { - rawKeys = await readAllKeys(); - } - let date = getFsDateString(); - - console.info(``); - console.info(`Decrypting...`); - console.info(``); -<<<<<<< Updated upstream - console.info( - `Send Đ${effectiveDash} to your staking key via the QR above, or its address:`, - ); - console.info(`${addr}`); - console.info( - `(this key will be used to fund and control your CrowdNode account)`, - ); -======= - await rawKeys.reduce(async function (promise, key) { - await promise; - - if (!key.encrypted) { - console.info(`📖 ${key.addr} [already decrypted]`); - return; - } - await safeSave( - Path.join(keysDir, `${key.addr}.wif`), - key.wif, - Path.join(keysDir, `${key.addr}.${date}.bak`), - ); - console.info(`🔓 ${key.addr}`); - }, Promise.resolve()); ->>>>>>> Stashed changes - console.info(``); - console.info(`Done ${DONE}`); - console.info(``); -} - -function getFsDateString() { - // YYYY-MM-DD_hh-mm_ss - let date = new Date() - .toISOString() - .replace(/:/g, ".") - .replace(/T/, "_") - .replace(/\.\d{3}.*/, ""); - return date; -} - -/** - * @param {String} filepath - * @param {String} wif - * @param {String} bakpath - */ -async function safeSave(filepath, wif, bakpath) { - let tmpPath = `${bakpath}.tmp`; - await Fs.writeFile(tmpPath, wif, "utf8"); - let err = await Fs.access(filepath).catch(Object); - if (!err) { - await Fs.rename(filepath, bakpath); - } - await Fs.rename(tmpPath, filepath); - if (!err) { - await Fs.unlink(bakpath); - } -} - -/** - * @typedef {Object} RawKey - * @property {String} addr - * @property {Boolean} encrypted - * @property {String} wif - */ -<<<<<<< Updated upstream -// ex: node ./bin/crowdnode.js transfer Xxxxx 'pub' 0.01 -async function transferBalance( - { dashApi, defaultAddr, forceConfirm, insightBaseUrl, insightApi }, - args, -) { - /** @type Array */ - let getAddrArgs = []; - - // There are two cases in which we could have only 2 arguments, - // and the first argument could be an address inside or outside - // of the wallet. - // - // Ex: - // crowdnode transfer {source} {dest} - // crowdnode transfer {source} {dest} {amount} - // crowdnode transfer {dest} {amount} - // crowdnode transfer {dest} - // - // To disambiguate, we check if the second argument is an amount. - if (3 === args.length) { - getAddrArgs = args; - } else if (2 === args.length) { - let maybeAmount = parseFloat(args[1]); - let isAddr = isNaN(maybeAmount); - if (isAddr) { - getAddrArgs = args; - } - } - let wif = await mustGetWif({ defaultAddr }, getAddrArgs); -======= ->>>>>>> Stashed changes - -/** - * @throws - */ -async function readAllKeys() { - let wifnames = await listManagedKeynames(); - - /** @type Array */ - let keys = []; - await wifnames.reduce(async function (promise, wifname) { - await promise; - - let keypath = Path.join(keysDir, wifname); - let key = await maybeReadKeyFileRaw(keypath); - if (!key?.wif) { - return; - } - - if (`${key.addr}.wif` !== wifname) { - throw new Error( - `computed pubkey '${key.addr}' of WIF does not match filename '${keypath}'`, - ); - } - - keys.push(key); - }, Promise.resolve()); - - return keys; -} - -/** - * @param {String} filepath - * @param {Object} [opts] - * @param {Boolean} opts.wif - * @returns {Promise} - */ -async function maybeReadKeyFile(filepath, opts) { - let key = await maybeReadKeyFileRaw(filepath, opts); - if (false === opts?.wif) { - return key?.addr || ""; - } - return key?.wif || ""; -} - -/** - * @param {String} filepath - * @param {Object} [opts] - * @param {Boolean} opts.wif - * @returns {Promise} - */ -<<<<<<< Updated upstream -async function sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args) { - let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); - let hotwallet = CrowdNode.main.hotwallet; - let state = await getCrowdNodeStatus({ addr, hotwallet }); - let balanceInfo = await dashApi.getInstantBalance(addr); - - if (state.status?.signup) { - console.info(`${addr} is already signed up. Here's the account status:`); - console.info(` ${state.signup} SignUpForApi`); - console.info(` ${state.accept} AcceptTerms`); - console.info(` ${state.deposit} DepositReceived`); - return; -======= -async function maybeReadKeyFileRaw(filepath, opts) { - let privKey = await Fs.readFile(filepath, "utf8").catch( - emptyStringOnErrEnoent, - ); - privKey = privKey.trim(); - if (!privKey) { - return null; ->>>>>>> Stashed changes - } - - let encrypted = false; - if (privKey.includes(":")) { - encrypted = true; - try { - if (false !== opts?.wif) { - privKey = await decrypt(privKey); - } - } catch (err) { - //@ts-ignore - console.error(err.message); - console.error(`passphrase does not match for key ${filepath}`); - process.exit(1); - } - } - if (false === opts?.wif) { - return { - addr: Path.basename(filepath, ".wif"), - encrypted: encrypted, - wif: "", - }; - } - - let pk = new Dashcore.PrivateKey(privKey); - let pub = pk.toAddress().toString(); - - return { - addr: pub, - encrypted: encrypted, - wif: privKey, - }; -} - -// tuple example {Promise<[String, Boolean]>} -/** - * @param {Object} [opts] - * @param {Boolean} [opts.force] - * @param {String} plainWif - */ -async function maybeEncrypt(plainWif, opts) { - let passphrase = cmds._getPassphrase(); - if (!passphrase) { - let result = await cmds.getPassphrase({}, []); - passphrase = result.passphrase; - } - if (!passphrase) { - if (opts?.force) { - throw new Error(`no passphrase with which to encrypt file`); - } - return plainWif; - } - - let key128 = await Cipher.deriveKey(passphrase); - let cipher = Cipher.create(key128); - return cipher.encrypt(plainWif); -} - -// TODO option to specify config dir - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.defaultAddr - * @param {Array} 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.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 {String} name - ex: Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.wif.enc - */ -function isNamedLikeKey(name) { - // TODO distinguish with .enc extension? - let hasGoodLength = 34 + 4 === name.length || 34 + 4 + 4 === name.length; - let knownExt = name.endsWith(".wif") || name.endsWith(".wif.enc"); - let isTmp = name.startsWith(".") || name.startsWith("_"); - return hasGoodLength && knownExt && !isTmp; -} - -/** - * @param {Object} opts - * @param {any} opts.dashApi - TODO - * @param {String} opts.addr - * @param {String} opts.filepath - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -async function removeKey({ addr, dashApi, filepath, insightBaseUrl }, args) { - let balanceInfo = await dashApi.getInstantBalance(addr); - - let balanceDash = toDash(balanceInfo.balanceSat); - if (balanceInfo.balanceSat) { - console.error(``); - console.error(`Error: ${addr}`); - console.error( - ` still has a balance of ${balanceInfo.balanceSat} (Đ${balanceDash})`, - ); - console.error(` (transfer to another address before deleting)`); - console.error(``); - process.exit(1); - return; - } - - await initCrowdNode(insightBaseUrl); - let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); - if (!crowdNodeBalance) { - // may be janky if not registered - crowdNodeBalance = {}; - } - if (!crowdNodeBalance.TotalBalance) { - crowdNodeBalance.TotalBalance = 0; - } - let crowdNodeDash = toDash(crowdNodeBalance.TotalBalance); - if (crowdNodeBalance.TotalBalance) { - console.error(``); - console.error(`Error: ${addr}`); - console.error( - ` still staking ${crowdNodeBalance.TotalBalance} (Đ${crowdNodeDash}) on CrowdNode`, - ); - console.error( - ` (withdrawal 100.0 and transfer to another address before deleting)`, - ); - console.error(``); - process.exit(1); - return; - } - -<<<<<<< Updated upstream - // this would allow for at least 2 withdraws costing (21000 + 1000) - let reserve = 50000; - let reserveDash = toDash(reserve); - if (!noReserve) { - console.info( - `reserving ${reserve} (Đ${reserveDash}) for withdraws (--no-reserve to disable)`, - ); -======= - let wifname = await findWif(addr); - let fullpath = Path.join(keysDir, wifname); - let wif = await maybeReadKeyPaths(filepath, { wif: true }); - - await Fs.unlink(fullpath).catch(function (err) { - console.error(`could not remove ${filepath}: ${err.message}`); - process.exit(1); - }); - - let wifnames = await listManagedKeynames(); - console.info(``); - console.info(`No balances found. Removing ${filepath}.`); - console.info(``); - console.info(`Backup (just in case):`); - console.info(` ${wif}`); - console.info(``); - if (!wifnames.length) { - console.info(`No keys left.`); - console.info(``); ->>>>>>> Stashed changes - } else { - let newAddr = wifnames[0]; - debug(`Selected ${newAddr} as new default staking key.`); - await Fs.writeFile(defaultWifPath, addr.replace(".wif", ""), "utf8"); - console.info(``); - } -} - -/** - * @param {String} pre - */ -async function findWif(pre) { - if (!pre) { - return ""; - } - - let names = await listManagedKeynames(); - names = names.filter(function (name) { - return name.startsWith(pre); - }); - - if (!names.length) { - return ""; - } - - if (names.length > 1) { - console.error(`'${pre}' is ambiguous:`, names.join(", ")); - process.exit(1); - return ""; - } - - return names[0]; -} - -async function listManagedKeynames() { - let nodes = await Fs.readdir(keysDir); - - return nodes.filter(isNamedLikeKey); -} - -/** - * @param {Object} opts - * @param {String} opts.defaultAddr - * @param {String} opts.insightBaseUrl - * @param {Array} args - */ -<<<<<<< Updated upstream -async function withdrawDash({ dashApi, defaultAddr, insightBaseUrl }, args) { -======= -async function loadAddr({ defaultAddr, insightBaseUrl }, args) { ->>>>>>> Stashed changes - let [addr] = await mustGetAddr({ defaultAddr }, args); - -<<<<<<< Updated upstream - if (!state.status?.accept) { - console.error(`no account for address ${addr}`); - process.exit(1); - return; - } - - 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: withdraw percent must be between 0.1 and 100.0"); - process.exit(1); - } - let percent = parseFloat(percentStr); - - let permil = Math.round(percent * 10); - if (permil <= 0 || permil > 1000) { - console.error("Error: withdraw percent must be between 0.1 and 100.0"); - process.exit(1); - } - - let realPercentStr = (permil / 10).toFixed(1); - console.info(`Initiating withdraw of ${realPercentStr}%...`); - - let wifname = await findWif(addr); - let filepath = Path.join(keysDir, wifname); - let wif = await maybeReadKeyFile(filepath); - let paid = await CrowdNode.withdraw(wif, hotwallet, permil); - //let paidFloat = (paid.satoshis / DUFFS).toFixed(8); - //let paidInt = paid.satoshis.toString().padStart(9, "0"); - console.info(`API Response: ${paid.api}`); -======= - let desiredAmountDash = parseFloat(args.shift() || "0"); - let desiredAmountDuff = Math.round(desiredAmountDash * DUFFS); - - let effectiveDuff = desiredAmountDuff; - let effectiveDash = ""; - if (!effectiveDuff) { - effectiveDuff = CrowdNode.stakeMinimum + signupTotal + feeEstimate; - effectiveDuff = roundDuff(effectiveDuff, 3); - effectiveDash = toDash(effectiveDuff); - } - - await plainLoadAddr({ addr, effectiveDash, effectiveDuff, insightBaseUrl }); - ->>>>>>> Stashed changes - return; -} - -/** - * @param {Object} opts - * @param {String} opts.addr - * @param {String} opts.effectiveDash - * @param {Number} opts.effectiveDuff - * @param {String} opts.insightBaseUrl - */ -async function plainLoadAddr({ - addr, - effectiveDash, - effectiveDuff, - insightBaseUrl, -}) { - console.info(``); - showQr(addr, effectiveDuff); - console.info(``); - console.info( - `Use the QR Code above to load ${effectiveDuff} (Đ${effectiveDash}) onto your staking key.`, - ); - console.info(``); - console.info(`(waiting...)`); - console.info(``); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); - console.info(`Received ${payment.satoshis}`); -} - -// Helpers - -/** - * Convert prefix, addr, keyname, or filepath to pub addr - * @param {String} name - * @throws - */ -async function wifFileToAddr(name) { - if (34 === name.length) { - // actually payment addr - return name; - } - - let privKey = ""; - - let wifname = await findWif(name); - if (wifname) { - let filepath = Path.join(keysDir, wifname); - privKey = await maybeReadKeyFile(filepath); - } - if (!privKey) { - privKey = await maybeReadKeyFile(name); - } - if (!privKey) { - throw new Error("bad file path or address"); - } - - let pk = new Dashcore.PrivateKey(privKey); - let pub = pk.toPublicKey().toAddress().toString(); - return pub; -} - -/** - * @param {Error & { code: String }} err - * @throws - */ -function emptyStringOnErrEnoent(err) { - if ("ENOENT" !== err.code) { - throw err; - } - return ""; -} - -/** - * @param {Number} duffs - ex: 00000000 - */ -function toDash(duffs) { - return (duffs / DUFFS).toFixed(8); -} - -/** - * @param {Number} duffs - ex: 00000000 - */ -function toDASH(duffs) { - let dash = (duffs / DUFFS).toFixed(8); - return `Đ` + dash.padStart(12, " "); -} - -/** - * @param {String} dash - ex: 0.00000000 - */ -function toDuff(dash) { - return Math.round(parseFloat(dash) * DUFFS); -} - -/** - * 1000 to Round to the nearest mDash - * ex: 0.50238108 => 0.50300000 - * @param {Number} effectiveDuff - * @param {Number} numDigits - */ -function roundDuff(effectiveDuff, numDigits) { - let n = Math.pow(10, numDigits); - let effectiveDash = toDash(effectiveDuff); - effectiveDuff = toDuff( - (Math.ceil(parseFloat(effectiveDash) * n) / n).toString(), - ); - return effectiveDuff; -} - -/** - * @param {Array} arr - * @param {any} item - */ -function removeItem(arr, item) { - let index = arr.indexOf(item); - if (index >= 0) { - return arr.splice(index, 1)[0]; - } - return null; -} - -// Run - -main().catch(function (err) { - console.error("Fail:"); - console.error(err.stack || err); - process.exit(1); -}); From 53d5b811ace9bdf05544e47fdc2857c73fb2cc4d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:38:38 -0700 Subject: [PATCH 02/10] ref!(v18): use separate base urls for insight sub-services --- bin/crowdnode.js | 103 ++++++++++++++++++++++------------------------ crowdnode.js | 58 ++++++++++++++++++++------ dashapi.js | 2 + package-lock.json | 31 +++++++------- package.json | 2 +- 5 files changed, 113 insertions(+), 83 deletions(-) diff --git a/bin/crowdnode.js b/bin/crowdnode.js index 8bd792c..e75b9a3 100755 --- a/bin/crowdnode.js +++ b/bin/crowdnode.js @@ -16,7 +16,7 @@ let Path = require("path"); let Cipher = require("./_cipher.js"); let CrowdNode = require("../crowdnode.js"); let Dash = require("../dashapi.js"); -let Insight = require("dashsight"); +let Dashsight = require("dashsight"); let Prompt = require("./_prompt.js"); let Qr = require("./_qr-node.js"); let Ws = require("dashsight/ws"); @@ -137,6 +137,13 @@ function showHelp() { let cmds = {}; +let dashsightBaseUrl = + process.env.DASHSIGHT_BASE_URL || "https://dashnode.duckdns.org/insight-api"; +let dashsocketBaseUrl = + process.env.DASHSOCKET_BASE_URL || "https://insight.dash.org/socket.io"; +let insightBaseUrl = + process.env.INSIGHT_BASE_URL || "https://insight.dash.org/insight-api"; + async function main() { /*jshint maxcomplexity:40 */ /*jshint maxstatements:500 */ @@ -180,17 +187,18 @@ async function main() { ); defaultAddr = defaultAddr.trim(); - let insightBaseUrl = - process.env.INSIGHT_BASE_URL || "https://insight.dash.org"; - let insightApi = Insight.create({ baseUrl: insightBaseUrl }); - let dashApi = Dash.create({ insightApi: insightApi }); + let dashsightApi = Dashsight.create({ + dashsightBaseUrl: dashsightBaseUrl, + dashsocketBaseUrl: dashsocketBaseUrl, + insightBaseUrl: insightBaseUrl, + }); + let dashApi = Dash.create({ insightApi: dashsightApi }); if ("stake" === subcommand) { await stakeDash( { dashApi, - insightApi, - insightBaseUrl, + insightApi: dashsightApi, defaultAddr, forceGenerate, noReserve, @@ -290,7 +298,12 @@ async function main() { // helper for debugging if ("transfer" === subcommand) { await transferBalance( - { dashApi, defaultAddr, forceConfirm, insightBaseUrl, insightApi }, + { + dashApi, + defaultAddr, + forceConfirm, + insightApi: dashsightApi, + }, args, ); process.exit(0); @@ -308,7 +321,7 @@ async function main() { let [addr] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); // ex: http (, ...) args.unshift(addr); let hasRpc = rpc in CrowdNode.http; @@ -339,7 +352,7 @@ async function main() { // keeping rm for backwards compat if ("rm" === subcommand || "delete" === subcommand) { - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let [addr, filepath] = await mustGetAddr({ defaultAddr }, args); await removeKey({ addr, dashApi, filepath, insightBaseUrl }, args); process.exit(0); @@ -377,10 +390,7 @@ async function main() { } if ("deposit" === subcommand) { - await depositDash( - { dashApi, defaultAddr, insightBaseUrl, noReserve }, - args, - ); + await depositDash({ dashApi, defaultAddr, noReserve }, args); process.exit(0); return; } @@ -416,14 +426,7 @@ async function main() { * @param {Array} args */ async function stakeDash( - { - dashApi, - defaultAddr, - forceGenerate, - insightApi, - insightBaseUrl, - noReserve, - }, + { dashApi, defaultAddr, forceGenerate, insightApi, noReserve }, args, ) { let err = await Fs.access(args[0]).catch(Object); @@ -446,6 +449,8 @@ async function stakeDash( console.info("Checking CrowdNode account... "); await CrowdNode.init({ baseUrl: "https://app.crowdnode.io", + dashsightBaseUrl, + dashsocketBaseUrl, insightBaseUrl, }); let hotwallet = CrowdNode.main.hotwallet; @@ -480,7 +485,6 @@ async function stakeDash( addr, effectiveDash, effectiveDuff, - insightBaseUrl, }); } @@ -492,7 +496,7 @@ async function stakeDash( } await depositDash( - { dashApi, defaultAddr: addr, insightBaseUrl, noReserve }, + { dashApi, defaultAddr: addr, noReserve }, [addr].concat(args), ); @@ -514,16 +518,15 @@ async function initKeystore({ defaultAddr }) { return defaultAddr || wifnames[0]; } -/** - * @param {String} insightBaseUrl - */ -async function initCrowdNode(insightBaseUrl) { +async function initCrowdNode() { if (CrowdNode.main.hotwallet) { return; } process.stdout.write("Checking CrowdNode API... "); await CrowdNode.init({ baseUrl: "https://app.crowdnode.io", + dashsightBaseUrl, + dashsocketBaseUrl, insightBaseUrl, }); console.info(`(hotwallet ${CrowdNode.main.hotwallet})`); @@ -1529,7 +1532,7 @@ async function removeKey({ addr, dashApi, filepath, insightBaseUrl }, args) { return; } - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let crowdNodeBalance = await CrowdNode.http.GetBalance(addr); if (!crowdNodeBalance) { // may be janky if not registered @@ -1659,12 +1662,7 @@ function roundDuff(effectiveDuff, numDigits) { * @param {Number} opts.effectiveDuff * @param {String} opts.insightBaseUrl */ -async function plainLoadAddr({ - addr, - effectiveDash, - effectiveDuff, - insightBaseUrl, -}) { +async function plainLoadAddr({ addr, effectiveDash, effectiveDuff }) { console.info(``); showQr(addr, effectiveDuff); console.info(``); @@ -1678,7 +1676,7 @@ async function plainLoadAddr({ console.info(``); console.info(`(waiting...)`); console.info(``); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); + let payment = await Ws.waitForVout(dashsocketBaseUrl, addr, 0); console.info(`Received ${payment.satoshis}`); } @@ -1707,7 +1705,7 @@ async function getBalance({ dashApi, defaultAddr }, args) { */ // ex: node ./bin/crowdnode.js transfer Xxxxx 'pub' 0.01 async function transferBalance( - { dashApi, defaultAddr, forceConfirm, insightBaseUrl, insightApi }, + { dashApi, defaultAddr, forceConfirm, insightApi }, args, ) { /** @type Array */ @@ -1764,7 +1762,7 @@ async function transferBalance( } process.exit(1); }, 30 * 1000); - await Ws.waitForVout(insightBaseUrl, newAddr, 0); + await Ws.waitForVout(dashsocketBaseUrl, newAddr, 0); console.info(`Accepted!`); return; } @@ -1778,7 +1776,7 @@ async function transferBalance( */ async function getStatus({ dashApi, defaultAddr, insightBaseUrl }, args) { let [addr] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let hotwallet = CrowdNode.main.hotwallet; let state = await getCrowdNodeStatus({ addr, hotwallet }); @@ -1816,7 +1814,7 @@ async function getStatus({ dashApi, defaultAddr, insightBaseUrl }, args) { */ async function sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args) { let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let hotwallet = CrowdNode.main.hotwallet; let state = await getCrowdNodeStatus({ addr, hotwallet }); let balanceInfo = await dashApi.getInstantBalance(addr); @@ -1831,7 +1829,7 @@ async function sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args) { let hasEnough = balanceInfo.balanceSat > signupOnly + feeEstimate; if (!hasEnough) { - await collectSignupFees(insightBaseUrl, addr); + await collectSignupFees(addr); } let wif = await maybeReadKeyPaths(name, { wif: true }); @@ -1853,7 +1851,7 @@ async function sendSignup({ dashApi, defaultAddr, insightBaseUrl }, args) { async function acceptTerms({ dashApi, defaultAddr, insightBaseUrl }, args) { let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let hotwallet = CrowdNode.main.hotwallet; let state = await getCrowdNodeStatus({ addr, hotwallet }); let balanceInfo = await dashApi.getInstantBalance(addr); @@ -1875,7 +1873,7 @@ async function acceptTerms({ dashApi, defaultAddr, insightBaseUrl }, args) { } let hasEnough = balanceInfo.balanceSat > acceptOnly + feeEstimate; if (!hasEnough) { - await collectSignupFees(insightBaseUrl, addr); + await collectSignupFees(addr); } let wif = await maybeReadKeyPaths(name, { wif: true }); @@ -1895,12 +1893,9 @@ async function acceptTerms({ dashApi, defaultAddr, insightBaseUrl }, args) { * @param {Boolean} opts.noReserve * @param {Array} args */ -async function depositDash( - { dashApi, defaultAddr, insightBaseUrl, noReserve }, - args, -) { +async function depositDash({ dashApi, defaultAddr, noReserve }, args) { let [addr, name] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let hotwallet = CrowdNode.main.hotwallet; let state = await getCrowdNodeStatus({ addr, hotwallet }); let balanceInfo = await dashApi.getInstantBalance(addr); @@ -1939,7 +1934,7 @@ async function depositDash( if (desiredAmountDuff) { ask = desiredAmountDuff + reserve + -balanceInfo.balanceSat; } - await collectDeposit(insightBaseUrl, addr, ask); + await collectDeposit(addr, ask); balanceInfo = await dashApi.getInstantBalance(addr); if (balanceInfo.balanceSat < needed) { let balanceDash = toDash(balanceInfo.balanceSat); @@ -1976,7 +1971,7 @@ async function depositDash( */ async function withdrawDash({ dashApi, defaultAddr, insightBaseUrl }, args) { let [addr] = await mustGetAddr({ defaultAddr }, args); - await initCrowdNode(insightBaseUrl); + await initCrowdNode(); let hotwallet = CrowdNode.main.hotwallet; let state = await getCrowdNodeStatus({ addr, hotwallet }); @@ -2050,7 +2045,7 @@ async function wifFileToAddr(name) { * @param {String} insightBaseUrl * @param {String} addr */ -async function collectSignupFees(insightBaseUrl, addr) { +async function collectSignupFees(addr) { console.info(``); showQr(addr); @@ -2068,7 +2063,7 @@ async function collectSignupFees(insightBaseUrl, addr) { console.info(""); console.info("(waiting...)"); console.info(""); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); + let payment = await Ws.waitForVout(dashsocketBaseUrl, addr, 0); console.info(`Received ${payment.satoshis}`); } @@ -2077,7 +2072,7 @@ async function collectSignupFees(insightBaseUrl, addr) { * @param {String} addr * @param {Number} duffAmount */ -async function collectDeposit(insightBaseUrl, addr, duffAmount) { +async function collectDeposit(addr, duffAmount) { console.info(``); showQr(addr, duffAmount); @@ -2097,7 +2092,7 @@ async function collectDeposit(insightBaseUrl, addr, duffAmount) { console.info(""); console.info("(waiting...)"); console.info(""); - let payment = await Ws.waitForVout(insightBaseUrl, addr, 0); + let payment = await Ws.waitForVout(dashsocketBaseUrl, addr, 0); console.info(`Received ${payment.satoshis}`); } diff --git a/crowdnode.js b/crowdnode.js index 65b1d0f..ef87976 100644 --- a/crowdnode.js +++ b/crowdnode.js @@ -13,12 +13,16 @@ let Dash = exports.DashApi || require("./dashapi.js"); let Dashcore = exports.dashcore || require("./lib/dashcore.js"); - let Insight = exports.DashSight || require("dashsight"); + let DashSight = exports.DashSight || require("dashsight"); let Ws = exports.DashSocket || require("dashsight/ws"); - CrowdNode._insightBaseUrl = ""; + CrowdNode._dashsocketBaseUrl = ""; // TODO don't require these shims - CrowdNode._insightApi = Insight.create({ baseUrl: "" }); + CrowdNode._insightApi = DashSight.create({ + dashsightBaseUrl: "", + dashsocketBaseUrl: "", + insightBaseUrl: "", + }); CrowdNode._dashApi = Dash.create({ insightApi: CrowdNode._insightApi }); CrowdNode.main = { @@ -83,17 +87,35 @@ /** * @param {Object} opts * @param {String} opts.baseUrl - * @param {String} opts.insightBaseUrl + * @param {String} opts.dashsightBaseUrl - typically ends with /insight-api + * @param {String} opts.dashsocketBaseUrl - typically ends with /socket.io + * @param {String} opts.insightBaseUrl - typically ends with /insight-api */ - CrowdNode.init = async function ({ baseUrl, insightBaseUrl }) { + CrowdNode.init = async function ({ + baseUrl, + dashsightBaseUrl, + dashsocketBaseUrl, + insightBaseUrl, + }) { // TODO use API // See https://github.com/dashhive/crowdnode.js/issues/3 CrowdNode._baseUrl = baseUrl; - CrowdNode._insightBaseUrl = insightBaseUrl; - CrowdNode._insightApi = Insight.create({ - baseUrl: insightBaseUrl, + if ("https://insight.dash.org" === insightBaseUrl) { + insightBaseUrl = "https://insight.dash.org/insight-api"; + if (!dashsocketBaseUrl) { + dashsocketBaseUrl = "https://insight.dash.org/socket.io"; + } + if (!dashsightBaseUrl) { + dashsightBaseUrl = "https://dashnode.duckdns.org/insight-api"; + } + } + CrowdNode._dashsocketBaseUrl = dashsocketBaseUrl; + CrowdNode._insightApi = DashSight.create({ + dashsightBaseUrl: dashsightBaseUrl, + dashsocketBaseUrl: dashsocketBaseUrl, + insightBaseUrl: insightBaseUrl, }); CrowdNode._dashApi = Dash.create({ insightApi: CrowdNode._insightApi }); }; @@ -175,7 +197,11 @@ await CrowdNode._insightApi.instantSend(tx.serialize()); let reply = CrowdNode.offset + CrowdNode.responses.PleaseAcceptTerms; - return await Ws.waitForVout(CrowdNode._insightBaseUrl, changeAddr, reply); + return await Ws.waitForVout( + CrowdNode._dashsocketBaseUrl, + changeAddr, + reply, + ); }; /** @@ -197,7 +223,11 @@ let reply = CrowdNode.offset + CrowdNode.responses.WelcomeToCrowdNodeBlockChainAPI; - return await Ws.waitForVout(CrowdNode._insightBaseUrl, changeAddr, reply); + return await Ws.waitForVout( + CrowdNode._dashsocketBaseUrl, + changeAddr, + reply, + ); }; /** @@ -230,7 +260,11 @@ await CrowdNode._insightApi.instantSend(tx.serialize()); let reply = CrowdNode.offset + CrowdNode.responses.DepositReceived; - return await Ws.waitForVout(CrowdNode._insightBaseUrl, changeAddr, reply); + return await Ws.waitForVout( + CrowdNode._dashsocketBaseUrl, + changeAddr, + reply, + ); }; /** @@ -266,7 +300,7 @@ satoshis: 0, txlock: false, }; - return await Ws.listen(CrowdNode._insightBaseUrl, findResponse); + return await Ws.listen(CrowdNode._dashsocketBaseUrl, findResponse); /** * @param {String} evname diff --git a/dashapi.js b/dashapi.js index e247f9b..b6eafff 100644 --- a/dashapi.js +++ b/dashapi.js @@ -207,6 +207,7 @@ /** * @param {Array} body + * @returns {Promise>} */ async function getUtxos(body) { /** @type Array */ @@ -243,6 +244,7 @@ data.vout.some(findAndSetUtxoIndex); + // TODO test without txid utxos.push({ txId: utxo.txid, outputIndex: utxoIndex, diff --git a/package-lock.json b/package-lock.json index 1345182..c4a1d6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,11 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", - "dashsight": "^1.1.1" + "dashsight": "^1.3.0-0" }, "bin": { "crowdnode": "bin/crowdnode.js" }, - "devDependencies": {}, "optionalDependencies": { "@root/request": "^1.9.1", "dotenv": "^16.0.1", @@ -89,9 +88,9 @@ "peer": true }, "node_modules/@root/request": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.1.tgz", - "integrity": "sha512-ROVgS5qgTvXDCI9XoJawJCB+QfC9jF7t3pFWmdUibjHqPt+2wV7pYSgth65qb65bK3qDIv9xXYF5FBECKhe8Iw==" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz", + "integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A==" }, "node_modules/@types/node": { "version": "12.20.55", @@ -281,11 +280,11 @@ } }, "node_modules/dashsight": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dashsight/-/dashsight-1.1.1.tgz", - "integrity": "sha512-fcNGAxbFlqh2p3Al0Jq+byexPK0HrQMYGzpvgINVs1DI+bQLgI1tjgbpNJf8l7uu5LVmDBq8tjVKoH+hCSzPPA==", + "version": "1.3.0-0", + "resolved": "https://registry.npmjs.org/dashsight/-/dashsight-1.3.0-0.tgz", + "integrity": "sha512-KR2Vj6Ts94FLRFr0A3xqa4PAf3R9njrN5k7IHzbzDZjohb+cG3+11e48W0Dvt6CQGC4+yToRnUvlrBmGdpU5TQ==", "dependencies": { - "@root/request": "^1.9.1" + "@root/request": "^1.9.2" }, "bin": { "dashsight-balance": "bin/balance.js", @@ -1286,9 +1285,9 @@ "peer": true }, "@root/request": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.1.tgz", - "integrity": "sha512-ROVgS5qgTvXDCI9XoJawJCB+QfC9jF7t3pFWmdUibjHqPt+2wV7pYSgth65qb65bK3qDIv9xXYF5FBECKhe8Iw==" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz", + "integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A==" }, "@types/node": { "version": "12.20.55", @@ -1442,11 +1441,11 @@ } }, "dashsight": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dashsight/-/dashsight-1.1.1.tgz", - "integrity": "sha512-fcNGAxbFlqh2p3Al0Jq+byexPK0HrQMYGzpvgINVs1DI+bQLgI1tjgbpNJf8l7uu5LVmDBq8tjVKoH+hCSzPPA==", + "version": "1.3.0-0", + "resolved": "https://registry.npmjs.org/dashsight/-/dashsight-1.3.0-0.tgz", + "integrity": "sha512-KR2Vj6Ts94FLRFr0A3xqa4PAf3R9njrN5k7IHzbzDZjohb+cG3+11e48W0Dvt6CQGC4+yToRnUvlrBmGdpU5TQ==", "requires": { - "@root/request": "^1.9.1", + "@root/request": "^1.9.2", "dotenv": "^16.0.1" } }, diff --git a/package.json b/package.json index 7146cde..86ee78f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "homepage": "https://github.com/dashhive/crowdnode.js#readme", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", - "dashsight": "^1.1.1" + "dashsight": "^1.3.0-0" }, "optionalDependencies": { "@root/request": "^1.9.1", From 202d30d399aafbaa64a6be72efd8f41a679e0316 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:39:13 -0700 Subject: [PATCH 03/10] 1.8.0-0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4a1d6f..ee084b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "crowdnode", - "version": "1.7.0", + "version": "1.8.0-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "crowdnode", - "version": "1.7.0", + "version": "1.8.0-0", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", diff --git a/package.json b/package.json index 86ee78f..0059cb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crowdnode", - "version": "1.7.0", + "version": "1.8.0-0", "description": "Manage your stake in Đash with the CrowdNode Blockchain API", "main": "./index.js", "browser": { From 69b599bb0f73157d379a301ea70ba558c4fcf3b0 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:55:30 -0700 Subject: [PATCH 04/10] fix(init): don't skip when uninitialized --- bin/crowdnode.js | 2 +- crowdnode.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/crowdnode.js b/bin/crowdnode.js index e75b9a3..efcf4d7 100755 --- a/bin/crowdnode.js +++ b/bin/crowdnode.js @@ -519,7 +519,7 @@ async function initKeystore({ defaultAddr }) { } async function initCrowdNode() { - if (CrowdNode.main.hotwallet) { + if (CrowdNode._initialized) { return; } process.stdout.write("Checking CrowdNode API... "); diff --git a/crowdnode.js b/crowdnode.js index ef87976..7b10a85 100644 --- a/crowdnode.js +++ b/crowdnode.js @@ -16,6 +16,7 @@ let DashSight = exports.DashSight || require("dashsight"); let Ws = exports.DashSocket || require("dashsight/ws"); + CrowdNode._initialized = false; CrowdNode._dashsocketBaseUrl = ""; // TODO don't require these shims CrowdNode._insightApi = DashSight.create({ @@ -118,6 +119,7 @@ insightBaseUrl: insightBaseUrl, }); CrowdNode._dashApi = Dash.create({ insightApi: CrowdNode._insightApi }); + CrowdNode._initialized = true; }; /** From ba0251ed38251478fdc4b546c6d118e215848395 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:55:40 -0700 Subject: [PATCH 05/10] chore(release): bump to v1.8.0-1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ee084b6..4cd59a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "crowdnode", - "version": "1.8.0-0", + "version": "1.8.0-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "crowdnode", - "version": "1.8.0-0", + "version": "1.8.0-1", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", diff --git a/package.json b/package.json index 0059cb9..9cfe14d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crowdnode", - "version": "1.8.0-0", + "version": "1.8.0-1", "description": "Manage your stake in Đash with the CrowdNode Blockchain API", "main": "./index.js", "browser": { From 3d28e25b119d15815703c3326a35f1832d2ae0a4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:58:31 -0700 Subject: [PATCH 06/10] fix(qr): re-export for node --- bin/_qr-node.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/_qr-node.js b/bin/_qr-node.js index 4d374a1..d614616 100644 --- a/bin/_qr-node.js +++ b/bin/_qr-node.js @@ -16,3 +16,5 @@ async function save(filepath, data, opts) { //@ts-ignore Qr.save = save; + +module.exports = Qr; From b91cace2b73a8efcef435dc1b7f4a9b411132dea Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 20:58:39 -0700 Subject: [PATCH 07/10] 1.8.0-2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cd59a9..941d0bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "crowdnode", - "version": "1.8.0-1", + "version": "1.8.0-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "crowdnode", - "version": "1.8.0-1", + "version": "1.8.0-2", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", diff --git a/package.json b/package.json index 9cfe14d..e0ebee5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crowdnode", - "version": "1.8.0-1", + "version": "1.8.0-2", "description": "Manage your stake in Đash with the CrowdNode Blockchain API", "main": "./index.js", "browser": { From e720e5757bfd011aacbf2e4d2c3a75bf1e74c51a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 12 Nov 2022 21:27:21 -0700 Subject: [PATCH 08/10] feat: swap duckdns for dashincubator.dev --- bin/crowdnode.js | 3 ++- crowdnode.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/crowdnode.js b/bin/crowdnode.js index efcf4d7..9b16f29 100755 --- a/bin/crowdnode.js +++ b/bin/crowdnode.js @@ -138,7 +138,8 @@ function showHelp() { let cmds = {}; let dashsightBaseUrl = - process.env.DASHSIGHT_BASE_URL || "https://dashnode.duckdns.org/insight-api"; + process.env.DASHSIGHT_BASE_URL || + "https://dashsight.dashincubator.dev/insight-api"; let dashsocketBaseUrl = process.env.DASHSOCKET_BASE_URL || "https://insight.dash.org/socket.io"; let insightBaseUrl = diff --git a/crowdnode.js b/crowdnode.js index 7b10a85..fa308ab 100644 --- a/crowdnode.js +++ b/crowdnode.js @@ -109,7 +109,7 @@ dashsocketBaseUrl = "https://insight.dash.org/socket.io"; } if (!dashsightBaseUrl) { - dashsightBaseUrl = "https://dashnode.duckdns.org/insight-api"; + dashsightBaseUrl = "https://dashsight.dashincubator.dev/insight-api"; } } CrowdNode._dashsocketBaseUrl = dashsocketBaseUrl; From dccbec82ed6aa3d8aeebefd2bc1c8bee63388164 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 14 Nov 2022 21:05:22 -0700 Subject: [PATCH 09/10] chore(release): bump to v1.8.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 941d0bf..e780bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "crowdnode", - "version": "1.8.0-2", + "version": "1.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "crowdnode", - "version": "1.8.0-2", + "version": "1.8.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@dashevo/dashcore-lib": "^0.19.41", diff --git a/package.json b/package.json index e0ebee5..7a885ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crowdnode", - "version": "1.8.0-2", + "version": "1.8.0", "description": "Manage your stake in Đash with the CrowdNode Blockchain API", "main": "./index.js", "browser": { From 1666d311567780fb1fe277150a0d2d6bb045616f Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 14 Nov 2022 21:16:26 -0700 Subject: [PATCH 10/10] chore: update cli submodule --- cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli b/cli index 949d278..ef8a233 160000 --- a/cli +++ b/cli @@ -1 +1 @@ -Subproject commit 949d278e580d7f43b703e988e9edbe34fdf441b6 +Subproject commit ef8a233106b074893ea569525cf3aaf4a987dc61