diff --git a/.jshintrc b/.jshintrc index 589cec4..e8e3216 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,4 +1,6 @@ { + "strict": false, + "module": true, "browser": true, "node": true, "esversion": 11, @@ -16,7 +18,6 @@ "plusplus": true, "undef": true, "unused": "vars", - "strict": true, "maxdepth": 4, "maxstatements": 100, "maxcomplexity": 20 diff --git a/bin/gobject-prepare.js b/bin/gobject-prepare.js index bcb5225..9050276 100755 --- a/bin/gobject-prepare.js +++ b/bin/gobject-prepare.js @@ -1,17 +1,12 @@ #!/usr/bin/env node "use strict"; -let DotEnv = require("dotenv"); -DotEnv.config({ path: ".env" }); -DotEnv.config({ path: ".env.secret" }); +import DashGov from "dashgov"; +import DashKeys from "dashkeys"; +import DashTx from "dashtx"; +import * as Secp256k1 from "@dashincubator/secp256k1"; -let DashGov = require("../"); -let DashRpc = require("dashrpc"); -let DashKeys = require("dashkeys"); -let DashTx = require("dashtx"); -let Secp256k1 = require("@dashincubator/secp256k1"); - -let Fs = require("node:fs/promises"); +import Fs from "node:fs/promises"; async function main() { /* jshint maxcomplexity: 100 */ @@ -20,7 +15,7 @@ async function main() { console.info(""); console.info("USAGE"); console.info( - " dashgov draft-proposal [start period] [num periods] <./collateral-key.wif> [network]", + " dashgov draft-proposal [start period] [num periods] <./burn-key.wif> [network]", ); console.info(""); console.info("EXAMPLE"); @@ -31,8 +26,14 @@ async function main() { /** @type {"mainnet"|"testnet"} */ let network = "mainnet"; + let rpcBasicAuth = `api:null`; + let rpcBaseUrl = `https://${rpcBasicAuth}@rpc.digitalcash.dev/`; + let rpcExplorer = "https://rpc.digitalcash.dev/"; + let isTestnet = takeFlag(process.argv, ["--testnet"]); if (isTestnet) { + rpcBaseUrl = `https://${rpcBasicAuth}@trpc.digitalcash.dev/`; + rpcExplorer = "https://trpc.digitalcash.dev/"; network = "testnet"; } @@ -42,61 +43,38 @@ async function main() { let proposalUrl = process.argv[5] || ""; let proposalName = process.argv[6] || ""; let paymentAddr = process.argv[7] || ""; - let collateralWifPath = process.argv[8] || ""; - let collateralWif = ""; - if (collateralWifPath) { - collateralWif = await Fs.readFile(collateralWifPath, "utf8"); - collateralWif = collateralWif.trim(); + let burnWifPath = process.argv[8] || ""; + let burnWif = ""; + if (burnWifPath) { + burnWif = await Fs.readFile(burnWifPath, "utf8"); + burnWif = burnWif.trim(); } - let rpcConfig = { - protocol: process.env.DASHD_RPC_PROTOCOL || "", - user: process.env.DASHD_RPC_USER || process.env.DASHD_RPC_USERNAME || "", - pass: process.env.DASHD_RPC_PASS || process.env.DASHD_RPC_PASSWORD || "", - host: process.env.DASHD_RPC_HOST || process.env.DASHD_RPC_HOSTNAME || "", - port: parseInt(process.env.DASHD_RPC_PORT || "", 10), - onconnected: function () { - console.info( - `[dashrpc] connected to '${rpcConfig.host}:${rpcConfig.port}'`, - ); - }, - }; - - if (!rpcConfig.protocol) { - throw new Error(`not set: export DASHD_RPC_PROTOCOL=`); - } - if (!rpcConfig.user) { - throw new Error(`not set: export DASHD_RPC_USERNAME=`); - } - if (!rpcConfig.pass) { - throw new Error(`not set: export DASHD_RPC_PASSWORD=`); - } - if (!rpcConfig.host) { - throw new Error(`not set: export DASHD_RPC_HOSTNAME=`); - } - if (!rpcConfig.port) { - throw new Error(`not set: export DASHD_RPC_PORT=`); + /** + * @param {String} method + * @param {...any} params + */ + async function rpc(method, ...params) { + let result = await DashTx.utils.rpc(rpcBaseUrl, method, ...params); + return result; } - let rpc = DashRpc.create(rpcConfig); - void (await rpc.init()); - - let tipsResult = await rpc.getBestBlockHash(); - let blockInfoResult = await rpc.getBlock(tipsResult.result, 1); - let blockHeight = blockInfoResult.result.height; - let blockMs = blockInfoResult.result.time * 1000; - // console.log(rootInfoResult.result, blockInfoResult.result, blockMs); + let tipsResult = await rpc("getbestblockhash"); + let blockInfoResult = await rpc("getblock", tipsResult, 1); + let blockHeight = blockInfoResult.height; + let blockMs = blockInfoResult.time * 1000; + // console.log(rootInfoResult, blockInfoResult, blockMs); // let blockTime = new Date(blockMs); // for testnet let blockDelta = 25000; - let rootHeight = blockInfoResult.result.height - blockDelta; - let rootResult = await rpc.getBlockHash(rootHeight); - let rootInfoResult = await rpc.getBlock(rootResult.result, 1); + let rootHeight = blockInfoResult.height - blockDelta; + let rootResult = await rpc("getblockhash", rootHeight); + let rootInfoResult = await rpc("getblock", rootResult, 1); let root = { block: rootHeight, - ms: rootInfoResult.result.time * 1000, + ms: rootInfoResult.time * 1000, }; // let rootTime = new Date(root.ms); @@ -138,7 +116,7 @@ async function main() { } /** - * @param {DashGov.Estimate} estimate + * @param {import('../dashgov.js').Estimate} estimate * @param {Number} i */ function show(estimate, i) { @@ -238,7 +216,6 @@ async function main() { return; } - /** @type {DashGov.GObjectData} */ let gobjData = DashGov.proposal.draftJson(selected, { name: proposalName, payment_address: paymentAddr, @@ -250,10 +227,10 @@ async function main() { let gobj = DashGov.proposal.draft(now, selected.start.startMs, gobjData, {}); console.log(gobj); - let gobjCollateralBytes = DashGov.serializeForCollateralTx(gobj); - let gobjCollateralHex = DashGov.utils.bytesToHex(gobjCollateralBytes); + let gobjBurnBytes = DashGov.serializeForBurnTx(gobj); + let gobjBurnHex = DashGov.utils.bytesToHex(gobjBurnBytes); - let gobjHashBytes = await DashGov.utils.doubleSha256(gobjCollateralBytes); + let gobjHashBytes = await DashGov.utils.doubleSha256(gobjBurnBytes); let gobjId = DashGov.utils.hashToId(gobjHashBytes); let gobjHashBytesReverse = gobjHashBytes.slice(); @@ -261,11 +238,11 @@ async function main() { let gobjIdForward = DashGov.utils.hashToId(gobjHashBytesReverse); console.log(""); - console.log("GObject Serialization (for hash for collateral memo)"); - console.log(gobjCollateralHex); + console.log("GObject Serialization (for hash for burn memo)"); + console.log(gobjBurnHex); console.log(""); - console.log("(Collateralized) GObject ID (for op return memo)"); + console.log("(Burnable) GObject ID (for op return memo)"); console.log(gobjIdForward); console.log("GObject ID (for 'gobject get ')"); console.log(gobjId); @@ -273,15 +250,15 @@ async function main() { let keyUtils = { /** * @param {DashTx.TxInputForSig} txInput - * @param {Number} i + * @param {Number} [i] */ getPrivateKey: async function (txInput, i) { - return DashKeys.wifToPrivKey(collateralWif, { version: network }); + return DashKeys.wifToPrivKey(burnWif, { version: network }); }, /** * @param {DashTx.TxInputForSig} txInput - * @param {Number} i + * @param {Number} [i] */ getPublicKey: async function (txInput, i) { let privKeyBytes = await keyUtils.getPrivateKey(txInput, i); @@ -295,7 +272,8 @@ async function main() { * @param {Uint8Array} txHashBytes */ sign: async function (privKeyBytes, txHashBytes) { - let sigOpts = { canonical: true, extraEntropy: true }; + // extraEntropy set to null to make gobject transactions idempotent + let sigOpts = { canonical: true, extraEntropy: null }; let sigBytes = await Secp256k1.sign(txHashBytes, privKeyBytes, sigOpts); return sigBytes; @@ -314,76 +292,87 @@ async function main() { let dashTx = DashTx.create(keyUtils); // dash-cli -testnet getaddressutxos '{"addresses":["yT6GS8qPrhsiiLHEaTWPYJMwfPPVt2SSFC"]}' - let collateralAddr = await DashKeys.wifToAddr(collateralWif, { + let burnAddr = await DashKeys.wifToAddr(burnWif, { version: network, }); console.log(""); - console.log("Collateral Address (source of 1 DASH network fee):"); - console.log(collateralAddr); + console.log("Burn Address (source of 1 DASH network fee):"); + console.log(burnAddr); - // we can set txid to short circuit for testing let txid = ""; - // ./bin/gobject-prepare.js 1 3 100 https://example.com/proposal-00 proposal-00 yPPy7Z5RQj46SnFtuFXyT6DFAygxESPR7K ./yjZxu7SJAwgSm1JtWybuQRYQDx34z8P2Z7.wif - // txid = "10d9862feb6eac6cf6fa653589e39b60a0ed640bae4140c51c35401ffe019479"; - if (!txid) { - let utxosResult = await rpc.getaddressutxos({ - addresses: [collateralAddr], + let txInfoSigned; + { + let utxosResult = await rpc("getaddressutxos", { + addresses: [burnAddr], }); // TODO make sure there's just 1 // @type {Array} */ - let inputs = [utxosResult.result[0]]; + let inputs = [utxosResult[0]]; // TODO the hash bytes may be reversed // @type {Array} */ let outputs = [{ memo: gobjIdForward, satoshis: 100000000 }]; let txInfo = { inputs, outputs }; - let txInfoSigned = await dashTx.hashAndSignAll(txInfo); + txInfoSigned = await dashTx.hashAndSignAll(txInfo); console.log(utxosResult); // console.log(""); - console.log("Signed Collateral Transaction (ready for broadcast):"); + console.log("Signed Burn Transaction (ready for broadcast):"); console.log(txInfoSigned.transaction); console.log(""); - console.log("Signed Collateral Transaction ID:"); + console.log("Signed Burn Transaction ID:"); txid = await DashTx.getId(txInfoSigned.transaction); console.log(txid); + } - let txResult = await rpc.request("/", { - method: "sendrawtransaction", - params: [txInfoSigned.transaction], - }); + async function check() { + let gobjResult = await rpc("gobject", "check", gobj.dataHex).catch( + /** @param {any} err */ function (err) { + console.error(err.message); + console.error(err.code); + console.error(err); + // invalid burn hash + return null; + }, + ); + + // { result: { 'Object status': 'OK' }, error: null, id: 5542 } + if (gobjResult?.["Object status"] !== "OK") { + throw new Error(`gobject failed: ${gobjResult.error}`); + } + return gobjResult; + } + + await check(); + + // ./bin/gobject-prepare.js 1 3 100 https://example.com/proposal-00 proposal-00 yPPy7Z5RQj46SnFtuFXyT6DFAygxESPR7K ./yjZxu7SJAwgSm1JtWybuQRYQDx34z8P2Z7.wif + // set to false to short circuit for testing + if (true) { + let txResult = await rpc("sendrawtransaction", txInfoSigned.transaction); console.log(""); console.log("Transaction sent:"); console.log(txResult); } for (;;) { - let txResult = await rpc - .request("/", { - method: "gettxoutproof", - params: [[txid]], - }) - .catch( - /** @param {Error} err */ function (err) { - const E_NOT_IN_BLOCK = -5; - // @ts-ignore - code exists - let code = err.code; - if (code === E_NOT_IN_BLOCK) { - return null; - } - throw err; - }, - ); + let txResult = await rpc("gettxoutproof", [txid]).catch( + /** @param {Error} err */ function (err) { + const E_NOT_IN_BLOCK = -5; + // @ts-ignore - code exists + let code = err.code; + if (code === E_NOT_IN_BLOCK) { + return null; + } + throw err; + }, + ); if (txResult) { console.log(""); console.log(`TxOutProof`); console.log(txResult); - let jsonResult = await rpc.request("/", { - method: "getrawtransaction", - params: [txid, 1], - }); + let jsonResult = await rpc("getrawtransaction", txid, 1); console.log(""); console.log(`Tx`); console.log(jsonResult); @@ -394,32 +383,13 @@ async function main() { await DashGov.utils.sleep(5000); } - // async function check() { - // let gobjResult = await rpc - // .request("/", { - // method: "gobject", - // params: ["check", gobj.dataHex], - // }) - // .catch( - // /** @param {Error} err */ function (err) { - // console.error(err.message); - // console.error(err.code); - // console.error(err); - // // invalid collateral hash - // return null; - // }, - // ); - - // return gobjResult; - // } - async function submit() { let req = { method: "gobject", params: [ "submit", - gobj.hashParent.toString(), // '0' must be a string for some reason - gobj.revision.toString(), + gobj.hashParent?.toString() || "0", // '0' must be a string for some reason + gobj.revision?.toString() || "1", gobj.time.toString(), gobj.dataHex, txid, @@ -427,13 +397,13 @@ async function main() { }; let args = req.params.join(" "); console.log(`${req.method} ${args}`); - let gobjResult = await rpc.request("/", req).catch( + let gobjResult = await rpc("gobject", ...req.params).catch( /** @param {Error} err */ function (err) { const E_INVALID_COLLATERAL = -32603; // @ts-ignore - code exists let code = err.code; if (code === E_INVALID_COLLATERAL) { - // wait for collateral to become valid + // wait for burn to become valid console.error(code, err.message); return null; } diff --git a/dashgov.js b/dashgov.js index 36fccf0..51f9e29 100644 --- a/dashgov.js +++ b/dashgov.js @@ -1,5 +1,3 @@ -/** @typedef {any} Gov - TODO */ - /** * This serialization is used exclusively for creating a hash to place in the OP_RETURN memo * of the collateral transaction. @@ -24,12 +22,12 @@ /** * @typedef GObjectData - * @prop {Uint53} end_epoch - whole seconds since epoch (like web-standard `exp`) + * @prop {Uint53} [end_epoch] - whole seconds since epoch (like web-standard `exp`) * @prop {String} name - kebab case (no spaces) * @prop {String} payment_address - base58-encoded p2pkh * @prop {Uint32} payment_amount - in whole DASH - * @prop {Uint53} start_epoch - whole seconds since epoch (like web-standard `iat`) - * @prop {Uint32} type - TODO + * @prop {Uint53} [start_epoch] - whole seconds since epoch (like web-standard `iat`) + * @prop {Uint32} [type] - TODO * @prop {String} url - conventionally dashcentral, with page the same as the 'name' */ @@ -64,576 +62,574 @@ * @prop {Array} upcoming - future voting periods */ -/** @type {Gov} */ -//@ts-ignore -var DashGov = ("object" === typeof module && exports) || {}; -(function (window, DashGov) { - "use strict"; +let DashGov = {}; +// Adapted from +// github.com/dashpay/dash/tree/develop/src/governance/common.cpp - // Adapted from - // github.com/dashpay/dash/tree/develop/src/governance/common.cpp +let Crypto = globalThis.crypto; - let Crypto = globalThis.crypto; +const LITTLE_ENDIAN = true; +const VARINT_8_MAX = 252; +const UINT_16_MAX = 65535; +const UINT_32_MAX = 4294967295; - const LITTLE_ENDIAN = true; - const VARINT_8_MAX = 252; - const UINT_16_MAX = 65535; - const UINT_32_MAX = 4294967295; +let textEncoder = new TextEncoder(); - let textEncoder = new TextEncoder(); +DashGov._type = 0b0000010; // from SER_GETHASH (bitwise enum) +DashGov._typeBytes = Uint8Array.from([0b0000010]); +DashGov._protocalVersion = 70231; // 0x00011257 (BE) => 0x57120100 (LE) +DashGov._protocalVersionBytes = Uint8Array.from([0x57, 0x12, 0x01, 0x00]); - DashGov._type = 0b0000010; // from SER_GETHASH (bitwise enum) - DashGov._typeBytes = Uint8Array.from([0b0000010]); - DashGov._protocalVersion = 70231; // 0x00011257 (BE) => 0x57120100 (LE) - DashGov._protocalVersionBytes = Uint8Array.from([0x57, 0x12, 0x01, 0x00]); - - DashGov.utils = {}; - - /** - * @param {Uint8Array} bytes - * @returns {String} hex - */ - DashGov.utils.bytesToHex = function bytesToHex(bytes) { - let hexes = []; - for (let i = 0; i < bytes.length; i += 1) { - let b = bytes[i]; - let h = b.toString(16); - h = h.padStart(2, "0"); - hexes.push(h); - } - let hex = hexes.join(""); - return hex; - }; +DashGov.utils = {}; - /** - * @param {Number} ms - */ - DashGov.utils.sleep = async function (ms) { - return new Promise(function (resolve) { - setTimeout(resolve, ms); - }); - }; +/** + * @param {Uint8Array} bytes + * @returns {String} hex + */ +DashGov.utils.bytesToHex = function bytesToHex(bytes) { + let hexes = []; + for (let i = 0; i < bytes.length; i += 1) { + let b = bytes[i]; + let h = b.toString(16); + h = h.padStart(2, "0"); + hexes.push(h); + } + let hex = hexes.join(""); + return hex; +}; - /** - * @param {Uint8Array} bytes - i.e. serialized gobj bytes - */ - DashGov.utils.doubleSha256 = async function (bytes) { - let hash1 = await Crypto.subtle.digest("SHA-256", bytes); - let hash2 = await Crypto.subtle.digest("SHA-256", hash1); - let gobjHash = new Uint8Array(hash2); +/** + * @param {Number} ms + */ +DashGov.utils.sleep = async function (ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms); + }); +}; - return gobjHash; - }; +/** + * @param {Uint8Array} bytes - i.e. serialized gobj bytes + */ +DashGov.utils.doubleSha256 = async function (bytes) { + let hash1 = await Crypto.subtle.digest("SHA-256", bytes); + let hash2 = await Crypto.subtle.digest("SHA-256", hash1); + let gobjHash = new Uint8Array(hash2); - /** - * @param {Uint8Array} hashBytes - */ - DashGov.utils.hashToId = function (hashBytes) { - let reverseBytes = hashBytes.slice(); - reverseBytes.reverse(); + return gobjHash; +}; - let id = DashGov.utils.bytesToHex(reverseBytes); - return id; - }; +/** + * @param {Uint8Array} hashBytes + */ +DashGov.utils.hashToId = function (hashBytes) { + let reverseBytes = hashBytes.slice(); + reverseBytes.reverse(); - /** - * Gets the number of bytes to store the number with VarInt "compression" - * - 1 byte for 0-252 (Uint8) - * - 1+2 bytes for 253 + Uint16 - * - 1+4 bytes for 254 + Uint32 - * - 1+8 bytes for 255 + Uint64 - * @param {Number} n - * @returns {1|3|5|9} - */ - DashGov.utils.toVarIntSize = function (n) { - if (n <= VARINT_8_MAX) { - return 1; - } + let id = DashGov.utils.bytesToHex(reverseBytes); + return id; +}; - if (n <= UINT_16_MAX) { - return 3; - } +/** + * Gets the number of bytes to store the number with VarInt "compression" + * - 1 byte for 0-252 (Uint8) + * - 1+2 bytes for 253 + Uint16 + * - 1+4 bytes for 254 + Uint32 + * - 1+8 bytes for 255 + Uint64 + * @param {Number} n + * @returns {1|3|5|9} + */ +DashGov.utils.toVarIntSize = function (n) { + if (n <= VARINT_8_MAX) { + return 1; + } - if (n <= UINT_32_MAX) { - return 5; - } + if (n <= UINT_16_MAX) { + return 3; + } - return 9; - }; + if (n <= UINT_32_MAX) { + return 5; + } - /** - * Writes `n` to `DataView` as a VarInt ("compressed" int). - * @param {DataView} dv - * @param {Number} offset - * @param {Number} n - * @returns void - */ - function writeVarInt(dv, offset, n) { - if (n <= VARINT_8_MAX) { - dv.setUint8(offset, n); - return; - } + return 9; +}; - let size; - if (n <= UINT_16_MAX) { - size = 253; - } else if (n <= UINT_32_MAX) { - size = 254; - } else { - size = 255; - } - dv.setUint8(offset, size); +/** + * Writes `n` to `DataView` as a VarInt ("compressed" int). + * @param {DataView} dv + * @param {Number} offset + * @param {Number} n + * @returns void + */ +function writeVarInt(dv, offset, n) { + if (n <= VARINT_8_MAX) { + dv.setUint8(offset, n); + return; + } - offset += 1; - let bigN = BigInt(n); - dv.setBigUint64(offset, bigN, LITTLE_ENDIAN); + let size; + if (n <= UINT_16_MAX) { + size = 253; + } else if (n <= UINT_32_MAX) { + size = 254; + } else { + size = 255; } + dv.setUint8(offset, size); - /** - * @param {GObject} gobj - */ - DashGov.serializeForCollateralTx = function ({ - hashParent = 0, - revision = 1, - time, - dataHex, - }) { - const varIntSize = DashGov.utils.toVarIntSize(dataHex.length); - - const dataLen = - 32 + // hashParent - 4 + // revision - 8 + // time - varIntSize + // compacted length header for HexStr(vchData) - dataHex.length + // HexStr(vchData) - 32 + - 4 + // masterNodeOutpoint (not used, so these bytes are the defaults) - 1 + - 4 + // dummy values to match old hashing - 1; // (varint) size of `vchSig` (always 1 byte to represent 0) - - const bytes = new Uint8Array(dataLen); - const dv = new DataView(bytes.buffer); - - let offset = 0; - - if (hashParent) { - bytes.set(hashParent, offset); - } - offset += 32; + offset += 1; + let bigN = BigInt(n); + dv.setBigUint64(offset, bigN, LITTLE_ENDIAN); +} - dv.setInt32(offset, revision, LITTLE_ENDIAN); - offset += 4; +/** + * @param {GObject} gobj + */ +DashGov.serializeForBurnTx = function ({ + hashParent = 0, + revision = 1, + time, + dataHex, +}) { + const varIntSize = DashGov.utils.toVarIntSize(dataHex.length); + + const dataLen = + 32 + // hashParent + 4 + // revision + 8 + // time + varIntSize + // compacted length header for HexStr(vchData) + dataHex.length + // HexStr(vchData) + 32 + + 4 + // masterNodeOutpoint (not used, so these bytes are the defaults) + 1 + + 4 + // dummy values to match old hashing + 1; // (varint) size of `vchSig` (always 1 byte to represent 0) + + const bytes = new Uint8Array(dataLen); + const dv = new DataView(bytes.buffer); + + let offset = 0; + + if (hashParent) { + bytes.set(hashParent, offset); + } + offset += 32; - let bigTime = BigInt(time); - dv.setBigInt64(offset, bigTime, LITTLE_ENDIAN); - offset += 8; - - void writeVarInt(dv, offset, dataHex.length); - offset += varIntSize; - let dataHexBytes = textEncoder.encode(dataHex); - bytes.set(dataHexBytes, offset); - offset += dataHex.length; - - { - // masternodeOutpointId (hash + index) is required for legacy reasons, - // but not used for collateral serialization - offset += 32; - - // Write out default mastNode `n` (index) - let masternodeOutpointIndex = 0xffffffff; - dv.setUint32(offset, masternodeOutpointIndex, LITTLE_ENDIAN); - offset += 4; - - // adding dummy values here to match old hashing - offset += 1; - dv.setUint32(offset, 0xffffffff, LITTLE_ENDIAN); - offset += 4; - } + dv.setInt32(offset, revision, LITTLE_ENDIAN); + offset += 4; - // the trailing 0 byte represents the VarInt Size of the vchSig, - // which is always 0 for collateral serialization - offset += 1; - return bytes; - }; + let bigTime = BigInt(time); + dv.setBigInt64(offset, bigTime, LITTLE_ENDIAN); + offset += 8; - // TODO move to a nice place - const SUPERBLOCK_INTERVAL = 16616; - const VOTE_LEAD_BLOCKS = 1662; - - // these are chosen by reason rather than by specification - const PROPOSAL_LEAD_MS = 6 * 24 * 60 * 60 * 1000; - const START_EPOCH_MS_BEFORE_VOTE = 23 * 24 * 60 * 60 * 1000; - const END_EPOCH_MS_AFTER_SUPERBLOCK = 4 * 24 * 60 * 60 * 1000; // after superblock - DashGov.PROPOSAL_LEAD_MS = PROPOSAL_LEAD_MS; - DashGov.SUPERBLOCK_INTERVAL = SUPERBLOCK_INTERVAL; - - // not used because the actual average at any time is always closer to 157.5 - //const SECONDS_PER_BLOCK_ESTIMATE = 155; - DashGov._AVG_SECS_PER_BLOCK = 157.5816652623977; - - // used to calculate ~5 year (~60 month) averages - const MONTHLY_SUPERBLOCK_01_DATE = "2017-03-05T20:16:05Z"; - const MONTHLY_SUPERBLOCK_01 = 631408; - const MONTHLY_SUPERBLOCK_61_DATE = "2022-02-26T03:53:02Z"; - const MONTHLY_SUPERBLOCK_61 = 1628368; - - /** - * @param {Snapshot} snapshot - * @param {Snapshot} root - * @returns {Float64} - fractional seconds - */ - DashGov.measureSecondsPerBlock = function (snapshot, root) { - let blockDelta = snapshot.block - root.block; - let timeDelta = snapshot.ms - root.ms; - let msPerBlock = timeDelta / blockDelta; - let sPerBlock = msPerBlock / 1000; - - return sPerBlock; - }; + void writeVarInt(dv, offset, dataHex.length); + offset += varIntSize; + let dataHexBytes = textEncoder.encode(dataHex); + bytes.set(dataHexBytes, offset); + offset += dataHex.length; - /** - * @param {Snapshot} [snapshot] - defaults to mainnet monthly superblock 61 - * @returns {Float64} - fractional seconds - */ - DashGov.estimateSecondsPerBlock = function (snapshot) { - if (!snapshot) { - snapshot = { - block: MONTHLY_SUPERBLOCK_61, - ms: Date.parse(MONTHLY_SUPERBLOCK_61_DATE), - }; - } - let root = { - block: MONTHLY_SUPERBLOCK_01, - ms: Date.parse(MONTHLY_SUPERBLOCK_01_DATE), - }; + { + // masternodeOutpointId (hash + index) is required for legacy reasons, + // but not used for collateral serialization + offset += 32; - let spb = DashGov.measureSecondsPerBlock(snapshot, root); - return spb; - }; + // Write out default mastNode `n` (index) + let masternodeOutpointIndex = 0xffffffff; + dv.setUint32(offset, masternodeOutpointIndex, LITTLE_ENDIAN); + offset += 4; - /** - * @param {Uint53} ms - the current time - * @param {Float64} secondsPerBlock - */ - DashGov.estimateBlockHeight = function (ms, secondsPerBlock) { - let then = Date.parse(MONTHLY_SUPERBLOCK_61_DATE); - let delta = ms - then; - let deltaS = delta / 1000; - let blocks = deltaS / secondsPerBlock; - blocks = Math.round(blocks); - - let height = MONTHLY_SUPERBLOCK_61 + blocks; - return height; - }; + // adding dummy values here to match old hashing + offset += 1; + dv.setUint32(offset, 0xffffffff, LITTLE_ENDIAN); + offset += 4; + } - /** - * @param {Estimates} estimates - * @param {Uint32} startPeriod - * @param {Uint32} endPeriod - */ - DashGov.selectEstimates = function (estimates, startPeriod, endPeriod) { - let startEstimate; - let endEstimate; - - if (startPeriod === 0) { - startEstimate = estimates.lameduck; - } else { - startPeriod -= 1; - startEstimate = estimates.upcoming[startPeriod]; - } - if (!startEstimate) { - throw new Error( - `${startPeriod} is not valid ('startPeriod' might not be a number)`, - ); - } + // the trailing 0 byte represents the VarInt Size of the vchSig, + // which is always 0 for collateral serialization + offset += 1; + return bytes; +}; + +// TODO move to a nice place +const SUPERBLOCK_INTERVAL = 16616; +const VOTE_LEAD_BLOCKS = 1662; + +// these are chosen by reason rather than by specification +const PROPOSAL_LEAD_MS = 6 * 24 * 60 * 60 * 1000; +const START_EPOCH_MS_BEFORE_VOTE = 23 * 24 * 60 * 60 * 1000; +const END_EPOCH_MS_AFTER_SUPERBLOCK = 4 * 24 * 60 * 60 * 1000; // after superblock +DashGov.PROPOSAL_LEAD_MS = PROPOSAL_LEAD_MS; +DashGov.SUPERBLOCK_INTERVAL = SUPERBLOCK_INTERVAL; + +// not used because the actual average at any time is always closer to 157.5 +//const SECONDS_PER_BLOCK_ESTIMATE = 155; +DashGov._AVG_SECS_PER_BLOCK = 157.5816652623977; + +// used to calculate ~5 year (~60 month) averages +const MONTHLY_SUPERBLOCK_01_DATE = "2017-03-05T20:16:05Z"; +const MONTHLY_SUPERBLOCK_01 = 631408; +const MONTHLY_SUPERBLOCK_61_DATE = "2022-02-26T03:53:02Z"; +const MONTHLY_SUPERBLOCK_61 = 1628368; - if (endPeriod === 0) { - endEstimate = estimates.lameduck; - } else { - endPeriod -= 1; - endEstimate = estimates.upcoming[endPeriod]; - } - if (!endEstimate) { - throw new Error( - `${endPeriod} is not valid ('count' might not be a number)`, - ); - } +/** + * @param {Snapshot} snapshot + * @param {Snapshot} root + * @returns {Float64} - fractional seconds + */ +DashGov.measureSecondsPerBlock = function (snapshot, root) { + let blockDelta = snapshot.block - root.block; + let timeDelta = snapshot.ms - root.ms; + let msPerBlock = timeDelta / blockDelta; + let sPerBlock = msPerBlock / 1000; - return { start: startEstimate, end: endEstimate }; - }; + return sPerBlock; +}; - DashGov.proposal = {}; - - /** - * @param {Object} selected - * @param {Estimate} selected.start - * @param {Estimate} selected.end - * @param {DashGov.GObjectData} proposalData - */ - DashGov.proposal.draftJson = function (selected, proposalData) { - let startEpoch = selected.start.startMs / 1000; - startEpoch = Math.round(startEpoch); - - let endEpoch = selected.end.endMs / 1000; - endEpoch = Math.round(endEpoch); - - let normalizedData = { - end_epoch: Math.round(endEpoch), - name: "", - payment_address: "", - payment_amount: 0, - start_epoch: Math.round(startEpoch), - type: 1, - url: "", +/** + * @param {Snapshot?} [snapshot] - defaults to mainnet monthly superblock 61 + * @returns {Float64} - fractional seconds + */ +DashGov.estimateSecondsPerBlock = function (snapshot) { + if (!snapshot) { + snapshot = { + block: MONTHLY_SUPERBLOCK_61, + ms: Date.parse(MONTHLY_SUPERBLOCK_61_DATE), }; - Object.assign(normalizedData, proposalData); - - return normalizedData; + } + let root = { + block: MONTHLY_SUPERBLOCK_01, + ms: Date.parse(MONTHLY_SUPERBLOCK_01_DATE), }; - /** - * Creates a draft object with reasonable and default values - * @param {Uint53} now - use Date.now(), except in testing - * @param {Uint53} startEpochMs - used to create a deterministic gobject time - * @param {GObjectData} data - will be sorted and hex-ified - * @param {GObject} [gobj] - override values - */ - DashGov.proposal.draft = function (now, startEpochMs, data, gobj) { - let dataHex = gobj?.dataHex || DashGov.proposal.sortAndEncodeJson(data); - let time = DashGov.proposal._selectKnownTime(now, startEpochMs); - - /** @type {DashGov.GObject} */ - let normalGObj = { - hashParent: 0, - revision: 1, - time: time, - dataHex: "", - masternodeOutpoint: null, - collateralTxId: null, - collateralTxOutputIndex: null, - signature: null, - }; - Object.assign(normalGObj, gobj, { dataHex }); + let spb = DashGov.measureSecondsPerBlock(snapshot, root); + return spb; +}; - return normalGObj; - }; +/** + * @param {Uint53} ms - the current time + * @param {Float64} secondsPerBlock + */ +DashGov.estimateBlockHeight = function (ms, secondsPerBlock) { + let then = Date.parse(MONTHLY_SUPERBLOCK_61_DATE); + let delta = ms - then; + let deltaS = delta / 1000; + let blocks = deltaS / secondsPerBlock; + blocks = Math.round(blocks); - /** - * @param {DashGov.GObjectData} normalizedData - */ - DashGov.proposal.sortAndEncodeJson = function (normalizedData) { - let keys = Object.keys(normalizedData); - keys.sort(); - - /** @type {GObjectData} */ - //@ts-ignore - let sortedData = {}; - for (let key of keys) { - //@ts-ignore - this is the same type as normalData, but future-proofed - sortedData[key] = normalizedData[key]; - } + let height = MONTHLY_SUPERBLOCK_61 + blocks; + return height; +}; - let textEncoder = new TextEncoder(); - let json = JSON.stringify(sortedData); - let jsonBytes = textEncoder.encode(json); - let hex = DashGov.utils.bytesToHex(jsonBytes); +/** + * @typedef Selection + * @prop {Estimate} start + * @prop {Estimate} end + */ - return hex; - }; +/** + * @param {Estimates} estimates + * @param {Uint32} startPeriod + * @param {Uint32} endPeriod + * @returns {Selection} + */ +DashGov.selectEstimates = function (estimates, startPeriod, endPeriod) { + let startEstimate; + let endEstimate; + + if (startPeriod === 0) { + startEstimate = estimates.lameduck; + } else { + startPeriod -= 1; + startEstimate = estimates.upcoming[startPeriod]; + } + if (!startEstimate) { + throw new Error( + `${startPeriod} is not valid ('startPeriod' might not be a number)`, + ); + } - /** - * The arbitrary use of random times is a leading cause of lost money during - * the proposal process, so instead we use the 'start epoch' when appropriate, - * or otherwise a UTC day interval of the start epoch - * @param {Uint53} now - * @param {Uint53} startMs - */ - DashGov.proposal._selectKnownTime = function (now, startMs) { - let startEpochDate = new Date(startMs); - let today = new Date(); - if (today < startEpochDate) { - let date = today.getUTCDate(); - startEpochDate.setUTCFullYear(today.getUTCFullYear()); - startEpochDate.setUTCMonth(today.getUTCMonth()); - startEpochDate.setUTCDate(date - 1); - } - let knownTimeMs = startEpochDate.valueOf(); - let knownSecs = knownTimeMs / 1000; - knownSecs = Math.floor(knownSecs); + if (endPeriod === 0) { + endEstimate = estimates.lameduck; + } else { + endPeriod -= 1; + endEstimate = estimates.upcoming[endPeriod]; + } + if (!endEstimate) { + throw new Error( + `${endPeriod} is not valid ('count' might not be a number)`, + ); + } - return knownSecs; - }; + return { start: startEstimate, end: endEstimate }; +}; - let msToHours = 60 * 60 * 1000; - /** - * Since the estimates are only accurate to within 30 minutes anyway, - * and since the extra entropy on the time just makes it more difficult to read, - * we just get rid of it. - * @param {Uint53} ms - */ - DashGov.proposal._roundDownToHour = function (ms) { - let timeF = ms / msToHours; - let time = Math.floor(timeF); - ms = time * msToHours; - return ms; +DashGov.proposal = {}; + +/** + * @param {Object} selected + * @param {Estimate} selected.start + * @param {Estimate} selected.end + * @param {GObjectData} proposalData + * @returns {Required} + */ +DashGov.proposal.draftJson = function (selected, proposalData) { + let startEpoch = selected.start.startMs / 1000; + startEpoch = Math.round(startEpoch); + + let endEpoch = selected.end.endMs / 1000; + endEpoch = Math.round(endEpoch); + + let normalizedData = { + end_epoch: Math.round(endEpoch), + name: "", + payment_address: "", + payment_amount: 0, + start_epoch: Math.round(startEpoch), + type: 1, + url: "", }; + Object.assign(normalizedData, proposalData); + + return normalizedData; +}; - /** - * @param {Uint53} ms - */ - DashGov.proposal._roundUpToHour = function (ms) { - let timeF = ms / msToHours; - let time = Math.ceil(timeF); - ms = time * msToHours; - return ms; +/** + * Creates a draft object with reasonable and default values + * @param {Uint53} now - use Date.now(), except in testing + * @param {Uint53} startEpochMs - used to create a deterministic gobject time + * @param {Required} data - will be sorted and hex-ified + * @param {Partial} [gobj] - override values + */ +DashGov.proposal.draft = function (now, startEpochMs, data, gobj) { + let dataHex = gobj?.dataHex || DashGov.proposal.sortAndEncodeJson(data); + let time = DashGov.proposal._selectKnownTime(now, startEpochMs); + + /** @type {GObject} */ + let normalGObj = { + hashParent: 0, + revision: 1, + time: time, + dataHex: "", + masternodeOutpoint: null, + collateralTxId: null, + collateralTxOutputIndex: null, + signature: null, }; + Object.assign(normalGObj, gobj, { dataHex }); - /** - * Note: since we're dealing with estimates that are typically reliable - * within an hour (and often even within 15 minutes), this may - * generate more results than it presents. - * @param {Uint8} [cycles] - 3 by default - * @param {Snapshot?} [snapshot] - * @param {Uint32} [proposalLeadtime] - default 3 days in ms - * @param {Float64} [secondsPerBlock] - typically close to 157.6 - * @returns {Estimates} - the last, due, and upcoming proposal cycles - */ - DashGov.estimateProposalCycles = function ( - cycles = 3, - snapshot = null, - secondsPerBlock = 0, - proposalLeadtime = PROPOSAL_LEAD_MS, - ) { - let now = snapshot?.ms || Date.now(); - let currentBlock = snapshot?.block; - if (!secondsPerBlock) { - if (currentBlock) { - snapshot = { block: currentBlock, ms: now }; - } - secondsPerBlock = DashGov.estimateSecondsPerBlock(snapshot); - } - if (!currentBlock) { - currentBlock = DashGov.estimateBlockHeight(now, secondsPerBlock); - } + return normalGObj; +}; - /** @type {Array} */ - let estimates = []; - for (let i = 0; i <= cycles + 1; i += 1) { - let estimate = DashGov.estimateNthNextGovCycle( - { block: currentBlock, ms: now }, - secondsPerBlock, - i, - ); - estimates.push(estimate); - } +/** + * @param {Required} normalizedData + */ +DashGov.proposal.sortAndEncodeJson = function (normalizedData) { + let keys = Object.keys(normalizedData); + keys.sort(); - { - /** @type {Estimate} */ - //@ts-ignore - we know there is at least one (past) estimate - let last = estimates.shift(); - - /** @type {Estimate?} */ - let lameduck = null; - if (estimates.length) { - if (estimates[0].voteDeltaMs < proposalLeadtime) { - //@ts-ignore - we just checked the length - lameduck = estimates.shift(); - } else { - // lose the extra cycle - void estimates.pop(); - } - } - let upcoming = estimates; + /** @type {GObjectData} */ + //@ts-ignore + let sortedData = {}; + for (let key of keys) { + //@ts-ignore - this is the same type as normalData, but future-proofed + sortedData[key] = normalizedData[key]; + } - return { - last, - lameduck, - upcoming, - }; - } - }; + let textEncoder = new TextEncoder(); + let json = JSON.stringify(sortedData); + let jsonBytes = textEncoder.encode(json); + let hex = DashGov.utils.bytesToHex(jsonBytes); - /** - * @param {Snapshot} snapshot - * @param {Float64} secondsPerBlock - * @param {Uint53} [offset] - how many superblocks in the future - * @returns {Estimate} - details about the current governance cycle - */ - DashGov.estimateNthNextGovCycle = function ( - snapshot, - secondsPerBlock, - offset = 0, - ) { - if (!secondsPerBlock) { - secondsPerBlock = DashGov.estimateSecondsPerBlock(snapshot); - } + return hex; +}; - let superblockHeight = DashGov.getNthNextSuperblock(snapshot.block, offset); +/** + * The arbitrary use of random times is a leading cause of lost money during + * the proposal process, so instead we use the 'start epoch' when appropriate, + * or otherwise a UTC day interval of the start epoch + * @param {Uint53} now + * @param {Uint53} startMs + */ +DashGov.proposal._selectKnownTime = function (now, startMs) { + let startEpochDate = new Date(startMs); + let today = new Date(); + if (today < startEpochDate) { + let date = today.getUTCDate(); + startEpochDate.setUTCFullYear(today.getUTCFullYear()); + startEpochDate.setUTCMonth(today.getUTCMonth()); + startEpochDate.setUTCDate(date - 1); + } + let knownTimeMs = startEpochDate.valueOf(); + let knownSecs = knownTimeMs / 1000; + knownSecs = Math.floor(knownSecs); - let superblockDelta = superblockHeight - snapshot.block; - let superblockDeltaMs = superblockDelta * secondsPerBlock * 1000; - let voteDeltaMs = VOTE_LEAD_BLOCKS * secondsPerBlock * 1000; + return knownSecs; +}; - let d = new Date(snapshot.ms); - d.setUTCMilliseconds(0); +let msToHours = 60 * 60 * 1000; +/** + * Since the estimates are only accurate to within 30 minutes anyway, + * and since the extra entropy on the time just makes it more difficult to read, + * we just get rid of it. + * @param {Uint53} ms + */ +DashGov.proposal._roundDownToHour = function (ms) { + let timeF = ms / msToHours; + let time = Math.floor(timeF); + ms = time * msToHours; + return ms; +}; - d.setUTCMilliseconds(superblockDeltaMs); - let sbms = d.valueOf(); - let sbts = d.toISOString(); +/** + * @param {Uint53} ms + */ +DashGov.proposal._roundUpToHour = function (ms) { + let timeF = ms / msToHours; + let time = Math.ceil(timeF); + ms = time * msToHours; + return ms; +}; - d.setUTCMilliseconds(-voteDeltaMs); - let vtms = d.valueOf(); - let vtts = d.toISOString(); +/** + * Note: since we're dealing with estimates that are typically reliable + * within an hour (and often even within 15 minutes), this may + * generate more results than it presents. + * @param {Uint8} [cycles] - 3 by default + * @param {Snapshot?} [snapshot] + * @param {Uint32} [proposalLeadtime] - default 3 days in ms + * @param {Float64} [secondsPerBlock] - typically close to 157.6 + * @returns {Estimates} - the last, due, and upcoming proposal cycles + */ +DashGov.estimateProposalCycles = function ( + cycles = 3, + snapshot = null, + secondsPerBlock = 0, + proposalLeadtime = PROPOSAL_LEAD_MS, +) { + let now = snapshot?.ms || Date.now(); + let currentBlock = snapshot?.block; + if (!secondsPerBlock) { + if (currentBlock) { + snapshot = { block: currentBlock, ms: now }; + } + secondsPerBlock = DashGov.estimateSecondsPerBlock(snapshot); + } + if (!currentBlock) { + currentBlock = DashGov.estimateBlockHeight(now, secondsPerBlock); + } - let startMs = vtms - START_EPOCH_MS_BEFORE_VOTE; - startMs = DashGov.proposal._roundDownToHour(startMs); - let startTime = new Date(startMs); + /** @type {Array} */ + let estimates = []; + for (let i = 0; i <= cycles + 1; i += 1) { + let estimate = DashGov.estimateNthNextGovCycle( + { block: currentBlock, ms: now }, + secondsPerBlock, + i, + ); + estimates.push(estimate); + } - let endMs = sbms + END_EPOCH_MS_AFTER_SUPERBLOCK; - endMs = DashGov.proposal._roundUpToHour(endMs); - let endTime = new Date(endMs); + { + /** @type {Estimate} */ + //@ts-ignore - we know there is at least one (past) estimate + let last = estimates.shift(); + + /** @type {Estimate?} */ + let lameduck = null; + if (estimates.length) { + if (estimates[0].voteDeltaMs < proposalLeadtime) { + //@ts-ignore - we just checked the length + lameduck = estimates.shift(); + } else { + // lose the extra cycle + void estimates.pop(); + } + } + let upcoming = estimates; return { - // TODO split into objects - startMs: startMs, - startIso: startTime.toISOString(), - voteHeight: superblockHeight - VOTE_LEAD_BLOCKS, - voteIso: vtts, - voteMs: vtms, - voteDelta: superblockDelta - VOTE_LEAD_BLOCKS, - voteDeltaMs: superblockDeltaMs - voteDeltaMs, - superblockHeight: superblockHeight, - superblockDelta: superblockDelta, - superblockIso: sbts, - superblockMs: sbms, - superblockDeltaMs: superblockDeltaMs, - endMs: endMs, - endIso: endTime.toISOString(), + last, + lameduck, + upcoming, }; + } +}; + +/** + * @param {Snapshot} snapshot + * @param {Float64} secondsPerBlock + * @param {Uint53} [offset] - how many superblocks in the future + * @returns {Estimate} - details about the current governance cycle + */ +DashGov.estimateNthNextGovCycle = function ( + snapshot, + secondsPerBlock, + offset = 0, +) { + if (!secondsPerBlock) { + secondsPerBlock = DashGov.estimateSecondsPerBlock(snapshot); + } + + let superblockHeight = DashGov.getNthNextSuperblock(snapshot.block, offset); + + let superblockDelta = superblockHeight - snapshot.block; + let superblockDeltaMs = superblockDelta * secondsPerBlock * 1000; + let voteDeltaMs = VOTE_LEAD_BLOCKS * secondsPerBlock * 1000; + + let d = new Date(snapshot.ms); + d.setUTCMilliseconds(0); + + d.setUTCMilliseconds(superblockDeltaMs); + let sbms = d.valueOf(); + let sbts = d.toISOString(); + + d.setUTCMilliseconds(-voteDeltaMs); + let vtms = d.valueOf(); + let vtts = d.toISOString(); + + let startMs = vtms - START_EPOCH_MS_BEFORE_VOTE; + startMs = DashGov.proposal._roundDownToHour(startMs); + let startTime = new Date(startMs); + + let endMs = sbms + END_EPOCH_MS_AFTER_SUPERBLOCK; + endMs = DashGov.proposal._roundUpToHour(endMs); + let endTime = new Date(endMs); + + return { + // TODO split into objects + startMs: startMs, + startIso: startTime.toISOString(), + voteHeight: superblockHeight - VOTE_LEAD_BLOCKS, + voteIso: vtts, + voteMs: vtms, + voteDelta: superblockDelta - VOTE_LEAD_BLOCKS, + voteDeltaMs: superblockDeltaMs - voteDeltaMs, + superblockHeight: superblockHeight, + superblockDelta: superblockDelta, + superblockIso: sbts, + superblockMs: sbms, + superblockDeltaMs: superblockDeltaMs, + endMs: endMs, + endIso: endTime.toISOString(), }; +}; - /** - * @param {Uint53} height - * @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth - * @returns {Uint53} - the superblock after the given height - */ - DashGov.getNthNextSuperblock = function (height, offset) { - let superblockCount = height / SUPERBLOCK_INTERVAL; - superblockCount = Math.floor(superblockCount); +/** + * @param {Uint53} height + * @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth + * @returns {Uint53} - the superblock after the given height + */ +DashGov.getNthNextSuperblock = function (height, offset) { + let superblockCount = height / SUPERBLOCK_INTERVAL; + superblockCount = Math.floor(superblockCount); - superblockCount += offset; - let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL; + superblockCount += offset; + let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL; - return superblockHeight; - }; + return superblockHeight; +}; - //@ts-ignore - window.DashGov = DashGov; -})(globalThis.window || {}, DashGov); -if ("object" === typeof module) { - module.exports = DashGov; -} +export default DashGov; /** @typedef {bigint} BigInt */ /** @typedef {Number} Uint8 */ diff --git a/jsconfig.json b/jsconfig.json index cf40d33..06fdf37 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -25,11 +25,14 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "es2022", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "paths": { + "dashgov": ["./dashgov.js"], + "dashgov/*": ["./*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ "typeRoots": ["./typings","./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ @@ -113,4 +116,4 @@ "src/**/*.js" ], "exclude": ["node_modules"] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 63a9916..ebb4dad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "dashgov", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dashgov", - "version": "0.1.0", + "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@dashincubator/secp256k1": "^1.7.1-5", "@types/node": "^22.1.0", "dashkeys": "^1.1.5", - "dashrpc": "^20.0.0", - "dashtx": "^0.18.2", + "dashtx": "^0.20.1", "dotenv": "^16.4.5" } }, @@ -39,18 +38,12 @@ "integrity": "sha512-ohHoe3bNeWZPsVxmOrWFaqZrJP3GeuSk6AtAawUCx0ZXVkTraeDQyMMp7ewhy3OEHkvs5yy6woMAQnwhmooX8w==", "dev": true }, - "node_modules/dashrpc": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/dashrpc/-/dashrpc-20.0.0.tgz", - "integrity": "sha512-43IEnwLs6x33OqLQC0qSBoePWcazXWP95maJxQnByGgRklz2kXzZ1v9RH50573YQfbo7iOVW76QBolr8xEwHgw==", - "dev": true, - "license": "MIT" - }, "node_modules/dashtx": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.18.2.tgz", - "integrity": "sha512-Q8X2Xkw2mtkd3FTtghSHi85AS/e7i98AVd8kHPL8PRLQMxYMPc3f3mvtVphM26ST+Fp2WNEjQZMIBgMWeZsECg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.20.1.tgz", + "integrity": "sha512-+Y0vxmkCzZ2+qrWgrHDxYust6io/jeMaNkcaPFI3ZjWcwVQEBnARyqWDTvHvfISUeROkPEUwEJ2GSOXY9Tnprg==", "dev": true, + "license": "SEE LICENSE IN LICENSE", "bin": { "dashtx-inspect": "bin/inspect.js" } diff --git a/package.json b/package.json index 69590c7..6fc4d85 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,19 @@ { "name": "dashgov", - "version": "0.1.0", + "version": "1.0.0", "description": "Utility functions for Dash governance on the blockchain.", "main": "dashgov.js", + "type": "module", + "files": [ + "./dashgov.js" + ], + "exports": { + ".": "./dashgov.js", + "./*": "./*" + }, + "imports": { + "dashgov": "./dashgov.js" + }, "scripts": { "bump": "npm version -m \"chore(release): bump to v%s\"", "fmt": "npm run prettier", @@ -11,7 +22,7 @@ "--------": "-------------------------------------------------", "jshint": "npx -p jshint@2.x -- jshint -c ./.jshintrc ./*.js", "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md}'", - "tsc": "npx -p typescript@5.x -- tsc -p ./jsconfig.json", + "tsc": "! npx -p typescript@5.x -- tsc -p ./jsconfig.json | grep '\\.js(\\d\\+,\\d\\+): error' | grep -v '\\