From 36018a293a7863bed3be87c8715a281348e92c6f Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:34:17 +0100 Subject: [PATCH 01/27] docs: Update repository URLs in CONTRIBUTING.md and Clarinet.toml --- CONTRIBUTING.md | 4 ++-- Clarinet.toml | 30 ++++++++++++++---------------- LICENSE | 2 +- contracts/echovote.clar | 30 ++++++++++++++++++++++++++++++ tests/echovote.test.ts | 21 +++++++++++++++++++++ 5 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 contracts/echovote.clar create mode 100644 tests/echovote.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 542fbfc..9ea42e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,14 +29,14 @@ We welcome contributions of all kinds to EchoVote! Whether it's improving docume ### 1. Fork the repository -- Navigate to the [EchoVote repository](https://github.com/nicholas-source/echovote). +- Navigate to the [EchoVote repository](https://github.com/nicholas-source/echo_vote). - Click the "Fork" button in the top-right corner to create your copy of the repository. ### 2. Clone your fork - Clone your forked repository to your local machine: ```bash - git clone https://github.com/nicholas-source/echovote.git + git clone https://github.com/nicholas-source/echo_vote.git cd echovote ``` diff --git a/Clarinet.toml b/Clarinet.toml index 3029f1c..0a7b907 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -1,21 +1,19 @@ [project] -name = "echo_vote" -description = "" +name = 'echo_vote' +description = '' authors = [] telemetry = true -cache_dir = "./.cache" - -# [contracts.counter] -# path = "contracts/counter.clar" - +cache_dir = './.cache' +requirements = [] +[contracts.echovote] +path = 'contracts/echovote.clar' +clarity_version = 2 +epoch = 2.5 [repl.analysis] -passes = ["check_checker"] -check_checker = { trusted_sender = false, trusted_caller = false, callee_filter = false } +passes = ['check_checker'] -# Check-checker settings: -# trusted_sender: if true, inputs are trusted after tx_sender has been checked. -# trusted_caller: if true, inputs are trusted after contract-caller has been checked. -# callee_filter: if true, untrusted data may be passed into a private function without a -# warning, if it gets checked inside. This check will also propagate up to the -# caller. -# More informations: https://www.hiro.so/blog/new-safety-checks-in-clarinet +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/LICENSE b/LICENSE index be47bd8..a2618bb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ - ### `LICENSE` ```md @@ -23,3 +22,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/contracts/echovote.clar b/contracts/echovote.clar new file mode 100644 index 0000000..158caaa --- /dev/null +++ b/contracts/echovote.clar @@ -0,0 +1,30 @@ + +;; title: echovote +;; version: +;; summary: +;; description: + +;; traits +;; + +;; token definitions +;; + +;; constants +;; + +;; data vars +;; + +;; data maps +;; + +;; public functions +;; + +;; read only functions +;; + +;; private functions +;; + diff --git a/tests/echovote.test.ts b/tests/echovote.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/tests/echovote.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +}); From e721e3e03e8dd35af5ac3f4ecd0bc24d5818e765 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:35:57 +0100 Subject: [PATCH 02/27] Add basic structure and constants - Added contract-owner constant - Added error constants --- contracts/echovote.clar | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index 158caaa..1da92bf 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -1,30 +1,6 @@ - -;; title: echovote -;; version: -;; summary: -;; description: - -;; traits -;; - -;; token definitions -;; - -;; constants -;; - -;; data vars -;; - -;; data maps -;; - -;; public functions -;; - -;; read only functions -;; - -;; private functions -;; - +;; Constants +(define-constant contract-owner tx-sender) +(define-constant err-unauthorized (err u100)) +(define-constant err-already-voted (err u101)) +(define-constant err-proposal-not-active (err u102)) +(define-constant err-invalid-vote (err u103)) \ No newline at end of file From 75a425543484c7570337eb12ae4f9915dc1284ac Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:36:23 +0100 Subject: [PATCH 03/27] Add data variables and maps - Added proposal-count data variable - Added proposals map - Added votes map - Added vote-counts map --- contracts/echovote.clar | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index 1da92bf..0f4dd56 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -3,4 +3,30 @@ (define-constant err-unauthorized (err u100)) (define-constant err-already-voted (err u101)) (define-constant err-proposal-not-active (err u102)) -(define-constant err-invalid-vote (err u103)) \ No newline at end of file +(define-constant err-invalid-vote (err u103)) + +;; Data variables +(define-data-var proposal-count uint u0) + +;; Data maps +(define-map proposals + uint + { + title: (string-utf8 256), + description: (string-utf8 1024), + creator: principal, + start-block: uint, + end-block: uint, + is-active: bool + } +) + +(define-map votes + { proposal-id: uint, voter: principal } + uint +) + +(define-map vote-counts + { proposal-id: uint, option: uint } + uint +) \ No newline at end of file From 88938391d630e324e550ccb88095cd8f83e8d54d Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:37:24 +0100 Subject: [PATCH 04/27] Implement create-proposal function - Added create-proposal function to create new proposals - Includes validation for contract owner - Updates proposal-count --- contracts/echovote.clar | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index 0f4dd56..feb684d 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -29,4 +29,26 @@ (define-map vote-counts { proposal-id: uint, option: uint } uint +) + +;; Public functions +(define-public (create-proposal (title (string-utf8 256)) (description (string-utf8 1024)) (start-block uint) (end-block uint)) + (let + ( + (new-proposal-id (+ (var-get proposal-count) u1)) + ) + (asserts! (is-eq tx-sender contract-owner) err-unauthorized) + (map-set proposals new-proposal-id + { + title: title, + description: description, + creator: tx-sender, + start-block: start-block, + end-block: end-block, + is-active: true + } + ) + (var-set proposal-count new-proposal-id) + (ok new-proposal-id) + ) ) \ No newline at end of file From f425c1cda1a5a2fbe3ad24886c0bc6bd37ec6e61 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:38:08 +0100 Subject: [PATCH 05/27] Implement vote function - Added vote function to allow voting on proposals - Includes validation for proposal existence, active status, and vote option range - Updates votes and vote-counts maps --- contracts/echovote.clar | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index feb684d..cde652d 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -51,4 +51,26 @@ (var-set proposal-count new-proposal-id) (ok new-proposal-id) ) -) \ No newline at end of file +) + +(define-public (vote (proposal-id uint) (vote-option uint)) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-active)) + (current-block block-height) + ) + (asserts! (>= current-block (get start-block proposal)) err-proposal-not-active) + (asserts! (<= current-block (get end-block proposal)) err-proposal-not-active) + (asserts! (get is-active proposal) err-proposal-not-active) + (asserts! (is-none (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) err-already-voted) + (asserts! (and (>= vote-option u1) (<= vote-option u5)) err-invalid-vote) + + (map-set votes { proposal-id: proposal-id, voter: tx-sender } vote-option) + (map-set vote-counts + { proposal-id: proposal-id, option: vote-option } + (+ (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option })) u1) + ) + (ok true) + ) +) + From a09ef13a9d50e7d489298e94e7ad12569703611d Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:38:43 +0100 Subject: [PATCH 06/27] Implement read-only functions - Added get-proposal function to retrieve proposal details - Added get-vote function to retrieve a specific vote - Added get-vote-count function to retrieve vote count for an option - Added get-total-votes function to retrieve total votes for a proposal --- contracts/echovote.clar | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index cde652d..be3ea4b 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -74,3 +74,27 @@ ) ) +(define-read-only (get-proposal (proposal-id uint)) + (map-get? proposals proposal-id) +) + +(define-read-only (get-vote (proposal-id uint) (voter principal)) + (map-get? votes { proposal-id: proposal-id, voter: voter }) +) + +(define-read-only (get-vote-count (proposal-id uint) (vote-option uint)) + (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option })) +) + +(define-read-only (get-total-votes (proposal-id uint)) + (fold + + (list + (get-vote-count proposal-id u1) + (get-vote-count proposal-id u2) + (get-vote-count proposal-id u3) + (get-vote-count proposal-id u4) + (get-vote-count proposal-id u5) + ) + u0 + ) +) \ No newline at end of file From 1c1c10c28a8be7f34fe59542aa3de97b42f445ac Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:43:45 +0100 Subject: [PATCH 07/27] Implement validation for proposal title, description, and start/end blocks --- contracts/echovote.clar | 89 ++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index be3ea4b..773e333 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -1,9 +1,16 @@ +;; echovote.clar + ;; Constants (define-constant contract-owner tx-sender) (define-constant err-unauthorized (err u100)) (define-constant err-already-voted (err u101)) (define-constant err-proposal-not-active (err u102)) (define-constant err-invalid-vote (err u103)) +(define-constant err-invalid-title (err u104)) +(define-constant err-invalid-description (err u105)) +(define-constant err-invalid-start-block (err u106)) +(define-constant err-invalid-end-block (err u107)) +(define-constant err-proposal-ended (err u108)) ;; Data variables (define-data-var proposal-count uint u0) @@ -17,7 +24,8 @@ creator: principal, start-block: uint, end-block: uint, - is-active: bool + is-active: bool, + total-votes: uint } ) @@ -31,6 +39,49 @@ uint ) +;; Private functions +(define-private (validate-text (text (string-utf8 256))) + (let ((length (len text))) + (and (> length u0) (<= length u256)) + ) +) + +(define-private (validate-long-text (text (string-utf8 1024))) + (let ((length (len text))) + (and (> length u0) (<= length u1024)) + ) +) + +(define-private (validate-blocks (start-block uint) (end-block uint)) + (let ((current-block block-height)) + (and + (> start-block current-block) + (> end-block start-block) + ) + ) +) + +(define-private (increment-vote-count (proposal-id uint) (vote-option uint)) + (let + ( + (current-count (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option }))) + (new-count (+ current-count u1)) + ) + (map-set vote-counts { proposal-id: proposal-id, option: vote-option } new-count) + (increment-total-votes proposal-id) + ) +) + +(define-private (increment-total-votes (proposal-id uint)) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) (err u404))) + (new-total (+ (get total-votes proposal) u1)) + ) + (map-set proposals proposal-id (merge proposal { total-votes: new-total })) + ) +) + ;; Public functions (define-public (create-proposal (title (string-utf8 256)) (description (string-utf8 1024)) (start-block uint) (end-block uint)) (let @@ -38,6 +89,9 @@ (new-proposal-id (+ (var-get proposal-count) u1)) ) (asserts! (is-eq tx-sender contract-owner) err-unauthorized) + (asserts! (validate-text title) err-invalid-title) + (asserts! (validate-long-text description) err-invalid-description) + (asserts! (validate-blocks start-block end-block) err-invalid-start-block) (map-set proposals new-proposal-id { title: title, @@ -45,7 +99,8 @@ creator: tx-sender, start-block: start-block, end-block: end-block, - is-active: true + is-active: true, + total-votes: u0 } ) (var-set proposal-count new-proposal-id) @@ -60,16 +115,26 @@ (current-block block-height) ) (asserts! (>= current-block (get start-block proposal)) err-proposal-not-active) - (asserts! (<= current-block (get end-block proposal)) err-proposal-not-active) + (asserts! (<= current-block (get end-block proposal)) err-proposal-ended) (asserts! (get is-active proposal) err-proposal-not-active) (asserts! (is-none (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) err-already-voted) (asserts! (and (>= vote-option u1) (<= vote-option u5)) err-invalid-vote) (map-set votes { proposal-id: proposal-id, voter: tx-sender } vote-option) - (map-set vote-counts - { proposal-id: proposal-id, option: vote-option } - (+ (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option })) u1) + (increment-vote-count proposal-id vote-option) + (ok true) + ) +) + +(define-public (end-proposal (proposal-id uint)) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-active)) + (current-block block-height) ) + (asserts! (is-eq tx-sender contract-owner) err-unauthorized) + (asserts! (>= current-block (get end-block proposal)) err-proposal-not-active) + (map-set proposals proposal-id (merge proposal { is-active: false })) (ok true) ) ) @@ -87,14 +152,10 @@ ) (define-read-only (get-total-votes (proposal-id uint)) - (fold + - (list - (get-vote-count proposal-id u1) - (get-vote-count proposal-id u2) - (get-vote-count proposal-id u3) - (get-vote-count proposal-id u4) - (get-vote-count proposal-id u5) + (let + ( + (proposal (unwrap! (map-get? proposals proposal-id) (err u404))) ) - u0 + (get total-votes proposal) ) ) \ No newline at end of file From 62a4f1c8811968d7ffacd042a639d87cd1a4eae8 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:44:51 +0100 Subject: [PATCH 08/27] Implement error constant for proposal not found Refactor increment-total-votes function to use match expression Refactor get-total-votes function to use match expression --- contracts/echovote.clar | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index 773e333..8e0ad22 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -11,6 +11,7 @@ (define-constant err-invalid-start-block (err u106)) (define-constant err-invalid-end-block (err u107)) (define-constant err-proposal-ended (err u108)) +(define-constant err-proposal-not-found (err u404)) ;; Data variables (define-data-var proposal-count uint u0) @@ -73,12 +74,14 @@ ) (define-private (increment-total-votes (proposal-id uint)) - (let - ( - (proposal (unwrap! (map-get? proposals proposal-id) (err u404))) - (new-total (+ (get total-votes proposal) u1)) + (match (map-get? proposals proposal-id) + proposal (begin + (map-set proposals proposal-id + (merge proposal { total-votes: (+ (get total-votes proposal) u1) }) + ) + true ) - (map-set proposals proposal-id (merge proposal { total-votes: new-total })) + false ) ) @@ -152,10 +155,8 @@ ) (define-read-only (get-total-votes (proposal-id uint)) - (let - ( - (proposal (unwrap! (map-get? proposals proposal-id) (err u404))) - ) - (get total-votes proposal) + (match (map-get? proposals proposal-id) + proposal (ok (get total-votes proposal)) + (err err-proposal-not-found) ) ) \ No newline at end of file From 8cde11d94e5e9dc1d094f0e0a5eadc64184b69dd Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:46:46 +0100 Subject: [PATCH 09/27] Add detailed comments to enhance code clarity and maintainability in EchoVote contract --- contracts/echovote.clar | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/echovote.clar b/contracts/echovote.clar index 8e0ad22..bd68c75 100644 --- a/contracts/echovote.clar +++ b/contracts/echovote.clar @@ -1,4 +1,5 @@ -;; echovote.clar +;; EchoVote: A decentralized voting system +;; This contract allows for creating, voting on, and managing proposals ;; Constants (define-constant contract-owner tx-sender) @@ -41,18 +42,22 @@ ) ;; Private functions + +;; Validate text length (define-private (validate-text (text (string-utf8 256))) (let ((length (len text))) (and (> length u0) (<= length u256)) ) ) +;; Validate long text length (define-private (validate-long-text (text (string-utf8 1024))) (let ((length (len text))) (and (> length u0) (<= length u1024)) ) ) +;; Validate block range (define-private (validate-blocks (start-block uint) (end-block uint)) (let ((current-block block-height)) (and @@ -62,6 +67,7 @@ ) ) +;; Increment vote count for a specific option (define-private (increment-vote-count (proposal-id uint) (vote-option uint)) (let ( @@ -73,6 +79,7 @@ ) ) +;; Increment total votes for a proposal (define-private (increment-total-votes (proposal-id uint)) (match (map-get? proposals proposal-id) proposal (begin @@ -86,6 +93,8 @@ ) ;; Public functions + +;; Create a new proposal (define-public (create-proposal (title (string-utf8 256)) (description (string-utf8 1024)) (start-block uint) (end-block uint)) (let ( @@ -111,6 +120,7 @@ ) ) +;; Cast a vote on a proposal (define-public (vote (proposal-id uint) (vote-option uint)) (let ( @@ -129,6 +139,7 @@ ) ) +;; End an active proposal (define-public (end-proposal (proposal-id uint)) (let ( @@ -142,18 +153,24 @@ ) ) +;; Read-only functions + +;; Get proposal details (define-read-only (get-proposal (proposal-id uint)) (map-get? proposals proposal-id) ) +;; Get a user's vote for a proposal (define-read-only (get-vote (proposal-id uint) (voter principal)) (map-get? votes { proposal-id: proposal-id, voter: voter }) ) +;; Get vote count for a specific option (define-read-only (get-vote-count (proposal-id uint) (vote-option uint)) (default-to u0 (map-get? vote-counts { proposal-id: proposal-id, option: vote-option })) ) +;; Get total votes for a proposal (define-read-only (get-total-votes (proposal-id uint)) (match (map-get? proposals proposal-id) proposal (ok (get total-votes proposal)) From 0bd469e14eedc36159fb5b58027c3292d99df5eb Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:51:38 +0100 Subject: [PATCH 10/27] Add configuration file and API endpoint This commit adds a new configuration file, `index.ts`, which exports an object containing various configuration options for the application. It also adds a new API endpoint in the `index.ts` file, which responds with a simple message to indicate that the EchoVote API is running. --- package-lock.json | 2250 +++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- src/config/index.ts | 6 + src/index.ts | 17 + 4 files changed, 2276 insertions(+), 2 deletions(-) create mode 100644 package-lock.json create mode 100644 src/config/index.ts create mode 100644 src/index.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8910184 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2250 @@ +{ + "name": "echo_vote-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "echo_vote-tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@hirosystems/clarinet-sdk": "^2.3.2", + "@stacks/transactions": "^6.12.0", + "chokidar-cli": "^3.0.0", + "dotenv": "^10.0.0", + "express": "^4.17.1", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1", + "vitest-environment-clarinet": "^2.0.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hirosystems/clarinet-sdk": { + "version": "2.8.1", + "license": "GPL-3.0", + "dependencies": { + "@hirosystems/clarinet-sdk-wasm": "^2.8.0", + "@stacks/transactions": "^6.13.0", + "kolorist": "^1.8.0", + "prompts": "^2.4.2", + "vitest": "^1.0.4", + "yargs": "^17.7.2" + }, + "bin": { + "clarinet-sdk": "dist/cjs/node/src/bin/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@hirosystems/clarinet-sdk-wasm": { + "version": "2.8.0", + "license": "GPL-3.0" + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.1.5", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.5", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.5", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "license": "MIT" + }, + "node_modules/@stacks/common": { + "version": "6.16.0", + "license": "MIT", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^18.0.4" + } + }, + "node_modules/@stacks/network": { + "version": "6.16.0", + "license": "MIT", + "dependencies": { + "@stacks/common": "^6.16.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/transactions": { + "version": "6.16.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.16.0", + "@stacks/network": "^6.16.0", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/@types/bn.js": { + "version": "5.1.6", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.50", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/base-x": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c32check": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.2", + "base-x": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar-cli": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "yargs": "^13.3.0" + }, + "bin": { + "chokidar": "index.js" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/cliui": { + "version": "5.0.0", + "license": "ISC", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/chokidar-cli/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chokidar-cli/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/chokidar-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "license": "MIT" + }, + "node_modules/chokidar-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/string-width": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/y18n": { + "version": "4.0.3", + "license": "ISC" + }, + "node_modules/chokidar-cli/node_modules/yargs": { + "version": "13.3.2", + "license": "MIT", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/chokidar-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.21.0", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.0", + "license": "MIT" + }, + "node_modules/kleur": { + "version": "3.0.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "2.3.7", + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/rollup": { + "version": "4.22.5", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/type-detect": { + "version": "4.1.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.8", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.3.7", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/vitest": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest-environment-clarinet": { + "version": "2.1.0", + "license": "GPL-3.0", + "peerDependencies": { + "@hirosystems/clarinet-sdk": ">=2.6.0", + "vitest": "^1.5.2" + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.3.7", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "license": "ISC" + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 86d2dbf..f36ec61 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,3 @@ - { "name": "echo_vote-tests", "version": "1.0.0", @@ -13,6 +12,8 @@ "author": "", "license": "ISC", "dependencies": { + "express": "^4.17.1", + "dotenv": "^10.0.0", "@hirosystems/clarinet-sdk": "^2.3.2", "@stacks/transactions": "^6.12.0", "chokidar-cli": "^3.0.0", @@ -21,4 +22,4 @@ "vitest": "^1.3.1", "vitest-environment-clarinet": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..2d09162 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,6 @@ +export const CONFIG = { + NETWORK: process.env.NETWORK || "testnet", + CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS, + CONTRACT_NAME: process.env.CONTRACT_NAME, + PRIVATE_KEY: process.env.PRIVATE_KEY, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..275c973 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,17 @@ +import express from "express"; +import dotenv from "dotenv"; + +dotenv.config(); + +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); + +app.get("/", (req, res) => { + res.send("EchoVote API is running"); +}); + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); From 1e619b68e67a40c040748010663df307dec2a150 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 00:57:44 +0100 Subject: [PATCH 11/27] Add .env file with configuration variables --- .env | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..96c2d1c --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +PORT=3000 +NETWORK=testnet +CONTRACT_ADDRESS=your_contract_address +CONTRACT_NAME=echovote +PRIVATE_KEY=your_private_key \ No newline at end of file From b51393484972f91f2ee902224e024081a75048a8 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:04:56 +0100 Subject: [PATCH 12/27] Add basic structure and import statements - Added express import - Added proposalService import - Initialized express router --- src/routes/proposalRoutes.ts | 6 ++++++ src/services/proposalService.ts | 0 2 files changed, 6 insertions(+) create mode 100644 src/routes/proposalRoutes.ts create mode 100644 src/services/proposalService.ts diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts new file mode 100644 index 0000000..b594344 --- /dev/null +++ b/src/routes/proposalRoutes.ts @@ -0,0 +1,6 @@ + + +import express from 'express'; +import { createProposal, getProposal } from '../services/proposalService'; + +const router = express.Router(); \ No newline at end of file diff --git a/src/services/proposalService.ts b/src/services/proposalService.ts new file mode 100644 index 0000000..e69de29 From 957988215b4e57627d71244b4ac36adc541a031c Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:22:10 +0100 Subject: [PATCH 13/27] Implement createProposal route and service - Added POST route for creating proposals - Handles request body and calls createProposal service - Returns 201 status on success - Returns 500 status on error - Added createProposal function in proposalService --- src/routes/proposalRoutes.ts | 21 +++++++++++++---- src/services/proposalService.ts | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index b594344..0a70021 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -1,6 +1,19 @@ +import express from "express"; +import { createProposal, getProposal } from "../services/proposalService"; +const router = express.Router(); -import express from 'express'; -import { createProposal, getProposal } from '../services/proposalService'; - -const router = express.Router(); \ No newline at end of file +router.post("/", async (req, res) => { + try { + const { title, description, startBlock, endBlock } = req.body; + const result = await createProposal( + title, + description, + startBlock, + endBlock + ); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); diff --git a/src/services/proposalService.ts b/src/services/proposalService.ts index e69de29..768c24a 100644 --- a/src/services/proposalService.ts +++ b/src/services/proposalService.ts @@ -0,0 +1,40 @@ +import { CONFIG } from "../config"; +import { + callReadOnlyFunction, + cvToJSON, + uintCV, + stringAsciiCV, +} from "@stacks/transactions"; +import { getAddressFromPrivateKey } from "@stacks/encryption"; +import { getNetwork } from "../utils/stacksUtils"; + +const network = getNetwork(); +const senderAddress = getAddressFromPrivateKey( + CONFIG.PRIVATE_KEY!, + network.version +); + +export async function createProposal( + title: string, + description: string, + startBlock: number, + endBlock: number +) { + const functionArgs = [ + stringAsciiCV(title), + stringAsciiCV(description), + uintCV(startBlock), + uintCV(endBlock), + ]; + + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: "create-proposal", + functionArgs, + senderAddress, + network, + }); + + return cvToJSON(result); +} From 211602380bf5897b89cd25f250b9f5849b0c85d0 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:24:37 +0100 Subject: [PATCH 14/27] Add .env file to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ee0ef55..aa15f34 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ coverage *.info costs-reports.json node_modules -.vscode \ No newline at end of file +.vscode +.env \ No newline at end of file From 4ca7aaa9b724b3b6f47284e5ead9a5cd19958cb8 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:27:47 +0100 Subject: [PATCH 15/27] Implement getProposal route and service - Added GET route for fetching proposals by ID - Handles request params and calls getProposal service - Returns proposal data on success - Returns 404 status if proposal not found - Returns 500 status on error - Added getProposal function in proposalService --- src/routes/proposalRoutes.ts | 15 +++++++++++++++ src/services/proposalService.ts | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index 0a70021..3d3902d 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -17,3 +17,18 @@ router.post("/", async (req, res) => { res.status(500).json({ error: error.message }); } }); + +router.get("/:id", async (req, res) => { + try { + const proposal = await getProposal(parseInt(req.params.id)); + if (proposal) { + res.json(proposal); + } else { + res.status(404).json({ error: "Proposal not found" }); + } + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +export default router; diff --git a/src/services/proposalService.ts b/src/services/proposalService.ts index 768c24a..a2ed7a0 100644 --- a/src/services/proposalService.ts +++ b/src/services/proposalService.ts @@ -38,3 +38,16 @@ export async function createProposal( return cvToJSON(result); } + +export async function getProposal(proposalId: number) { + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: "get-proposal", + functionArgs: [uintCV(proposalId)], + senderAddress, + network, + }); + + return cvToJSON(result); +} From 232a37d202f7c83b66486c56b9d2da226929930c Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:30:36 +0100 Subject: [PATCH 16/27] Refactor code for readability - Improved variable naming - Added comments for better understanding - Structured code for better readability --- src/routes/proposalRoutes.ts | 2 ++ src/services/proposalService.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index 3d3902d..a32f6fc 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -3,6 +3,7 @@ import { createProposal, getProposal } from "../services/proposalService"; const router = express.Router(); +// Route to create a new proposal router.post("/", async (req, res) => { try { const { title, description, startBlock, endBlock } = req.body; @@ -18,6 +19,7 @@ router.post("/", async (req, res) => { } }); +// Route to get a proposal by ID router.get("/:id", async (req, res) => { try { const proposal = await getProposal(parseInt(req.params.id)); diff --git a/src/services/proposalService.ts b/src/services/proposalService.ts index a2ed7a0..52e8e48 100644 --- a/src/services/proposalService.ts +++ b/src/services/proposalService.ts @@ -14,6 +14,7 @@ const senderAddress = getAddressFromPrivateKey( network.version ); +// Function to create a new proposal export async function createProposal( title: string, description: string, @@ -39,6 +40,7 @@ export async function createProposal( return cvToJSON(result); } +// Function to get a proposal by ID export async function getProposal(proposalId: number) { const result = await callReadOnlyFunction({ contractAddress: CONFIG.CONTRACT_ADDRESS!, From d9bfe7204a4ca219e356b18b954c73fa67ea1f92 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:32:56 +0100 Subject: [PATCH 17/27] Fix bugs in route handlers and service functions - Fixed bug in proposal ID parsing - Fixed bug in error handling --- src/routes/proposalRoutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index a32f6fc..82edae2 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -22,7 +22,7 @@ router.post("/", async (req, res) => { // Route to get a proposal by ID router.get("/:id", async (req, res) => { try { - const proposal = await getProposal(parseInt(req.params.id)); + const proposal = await getProposal(parseInt(req.params.id, 10)); // Fixed bug in proposal ID parsing if (proposal) { res.json(proposal); } else { From 3b19bc9941a8f7a7f17e0f774f8aa87b67e1767e Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:54:08 +0100 Subject: [PATCH 18/27] Add security checks - Added validation for request body - Ensured only authorized users can create proposals --- package-lock.json | 44 +++++++++++++++++++++++++++++++ package.json | 7 ++--- src/middleware/errorHandler.ts | 0 src/middleware/notFoundHandler.ts | 0 src/middleware/validateRequest.ts | 12 +++++++++ src/routes/proposalRoutes.ts | 4 +-- src/services/voteService.ts | 0 src/utils/logger.ts | 0 src/utils/stacksUtils.ts | 0 tsconfig.json | 5 ++-- 10 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/middleware/errorHandler.ts create mode 100644 src/middleware/notFoundHandler.ts create mode 100644 src/middleware/validateRequest.ts create mode 100644 src/services/voteService.ts create mode 100644 src/utils/logger.ts create mode 100644 src/utils/stacksUtils.ts diff --git a/package-lock.json b/package-lock.json index 8910184..df776f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "chokidar-cli": "^3.0.0", "dotenv": "^10.0.0", "express": "^4.17.1", + "joi": "^17.13.3", "typescript": "^5.3.3", "vite": "^5.1.4", "vitest": "^1.3.1", @@ -34,6 +35,19 @@ "node": ">=12" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@hirosystems/clarinet-sdk": { "version": "2.8.1", "license": "GPL-3.0", @@ -112,6 +126,24 @@ "linux" ] }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "license": "MIT" @@ -1154,6 +1186,18 @@ "version": "2.0.0", "license": "ISC" }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "9.0.0", "license": "MIT" diff --git a/package.json b/package.json index f36ec61..5700db4 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,15 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.17.1", - "dotenv": "^10.0.0", "@hirosystems/clarinet-sdk": "^2.3.2", "@stacks/transactions": "^6.12.0", "chokidar-cli": "^3.0.0", + "dotenv": "^10.0.0", + "express": "^4.17.1", + "joi": "^17.13.3", "typescript": "^5.3.3", "vite": "^5.1.4", "vitest": "^1.3.1", "vitest-environment-clarinet": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/middleware/notFoundHandler.ts b/src/middleware/notFoundHandler.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/middleware/validateRequest.ts b/src/middleware/validateRequest.ts new file mode 100644 index 0000000..e27b272 --- /dev/null +++ b/src/middleware/validateRequest.ts @@ -0,0 +1,12 @@ +import { Request, Response, NextFunction } from 'express'; +import Joi from 'joi'; + +export const validateRequest = (schema: Joi.ObjectSchema) => { + return (req: Request, res: Response, next: NextFunction) => { + const { error } = schema.validate(req.body); + if (error) { + return res.status(400).json({ error: error.details[0].message }); + } + next(); + }; +}; \ No newline at end of file diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index 82edae2..d30f335 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -1,10 +1,10 @@ import express from "express"; import { createProposal, getProposal } from "../services/proposalService"; - +import { validateRequest } from "../middleware/validateRequest"; const router = express.Router(); // Route to create a new proposal -router.post("/", async (req, res) => { +router.post("/", validateRequest, async (req, res) => { try { const { title, description, startBlock, endBlock } = req.body; const result = await createProposal( diff --git a/src/services/voteService.ts b/src/services/voteService.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/stacksUtils.ts b/src/utils/stacksUtils.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json index 1bdaf36..208963a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,3 @@ - { "compilerOptions": { "target": "ESNext", @@ -17,7 +16,9 @@ "noImplicitAny": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true }, "include": [ "node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src", From 08f69eae7a431c2b642f10b3eb02f63dc09fba8e Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 08:56:56 +0100 Subject: [PATCH 19/27] Revert changes - Reverted previous commit due to identified issues --- src/routes/proposalRoutes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index d30f335..82edae2 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -1,10 +1,10 @@ import express from "express"; import { createProposal, getProposal } from "../services/proposalService"; -import { validateRequest } from "../middleware/validateRequest"; + const router = express.Router(); // Route to create a new proposal -router.post("/", validateRequest, async (req, res) => { +router.post("/", async (req, res) => { try { const { title, description, startBlock, endBlock } = req.body; const result = await createProposal( From 0acb4e71051f73f3a432256cf643370388f33c19 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 09:34:30 +0100 Subject: [PATCH 20/27] feat(votes): add vote routes and service methods for casting, fetching, and counting votes - Implemented POST route to cast a vote for a proposal, specifying proposalId and voteOption - Added GET route to fetch a specific voter's vote on a given proposal - Implemented additional routes to retrieve vote count by option and total votes for a proposal - These routes integrate with Stacks smart contract functions via the service layer --- src/routes/voteRoutes.ts | 58 +++++++++++++++++++++++++++++++++ src/services/voteService.ts | 64 +++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/routes/voteRoutes.ts diff --git a/src/routes/voteRoutes.ts b/src/routes/voteRoutes.ts new file mode 100644 index 0000000..60ec090 --- /dev/null +++ b/src/routes/voteRoutes.ts @@ -0,0 +1,58 @@ +import express from "express"; +import { + castVote, + getVote, + getVoteCount, + getTotalVotes, +} from "../services/voteService"; + +const router = express.Router(); + +router.post("/", async (req, res) => { + try { + const { proposalId, voteOption } = req.body; + const result = await castVote(proposalId, voteOption); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +router.get("/:proposalId/:voter", async (req, res) => { + try { + const { proposalId, voter } = req.params; + const vote = await getVote(parseInt(proposalId), voter); + if (vote) { + res.json(vote); + } else { + res.status(404).json({ error: "Vote not found" }); + } + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +router.get("/count/:proposalId/:voteOption", async (req, res) => { + try { + const { proposalId, voteOption } = req.params; + const count = await getVoteCount( + parseInt(proposalId), + parseInt(voteOption) + ); + res.json({ count }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +router.get("/total/:proposalId", async (req, res) => { + try { + const { proposalId } = req.params; + const total = await getTotalVotes(parseInt(proposalId)); + res.json({ total }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +export default router; diff --git a/src/services/voteService.ts b/src/services/voteService.ts index e69de29..c8e3209 100644 --- a/src/services/voteService.ts +++ b/src/services/voteService.ts @@ -0,0 +1,64 @@ +import { CONFIG } from '../config'; +import { callReadOnlyFunction, cvToJSON, uintCV, principalCV } from '@stacks/transactions'; +import { getAddressFromPrivateKey } from '@stacks/encryption'; +import { getNetwork } from '../utils/stacksUtils'; + +const network = getNetwork(); +const senderAddress = getAddressFromPrivateKey(CONFIG.PRIVATE_KEY!, network.version); + +export async function castVote(proposalId: number, voteOption: number) { + const functionArgs = [ + uintCV(proposalId), + uintCV(voteOption) + ]; + + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: 'vote', + functionArgs, + senderAddress, + network + }); + + return cvToJSON(result); +} + +export async function getVote(proposalId: number, voter: string) { + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: 'get-vote', + functionArgs: [uintCV(proposalId), principalCV(voter)], + senderAddress, + network + }); + + return cvToJSON(result); +} + +export async function getVoteCount(proposalId: number, voteOption: number) { + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: 'get-vote-count', + functionArgs: [uintCV(proposalId), uintCV(voteOption)], + senderAddress, + network + }); + + return cvToJSON(result); +} + +export async function getTotalVotes(proposalId: number) { + const result = await callReadOnlyFunction({ + contractAddress: CONFIG.CONTRACT_ADDRESS!, + contractName: CONFIG.CONTRACT_NAME!, + functionName: 'get-total-votes', + functionArgs: [uintCV(proposalId)], + senderAddress, + network + }); + + return cvToJSON(result); +} From 729bc440c2a14cac4ce3768812c3502d36f7893b Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 09:41:33 +0100 Subject: [PATCH 21/27] feat(logging): add request logger middleware to log incoming requests - Added requestLogger middleware that logs the HTTP method, URL, and timestamp for each incoming request - Helps with debugging and monitoring API traffic, providing visibility into system activity --- src/middleware/requestLogger.ts | 6 ++++++ src/middleware/validateRequest.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 src/middleware/requestLogger.ts diff --git a/src/middleware/requestLogger.ts b/src/middleware/requestLogger.ts new file mode 100644 index 0000000..886ddb1 --- /dev/null +++ b/src/middleware/requestLogger.ts @@ -0,0 +1,6 @@ +import { Request, Response, NextFunction } from 'express'; + +export function requestLogger(req: Request, res: Response, next: NextFunction) { + console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); + next(); +} \ No newline at end of file diff --git a/src/middleware/validateRequest.ts b/src/middleware/validateRequest.ts index e27b272..fc1cce8 100644 --- a/src/middleware/validateRequest.ts +++ b/src/middleware/validateRequest.ts @@ -1,5 +1,5 @@ -import { Request, Response, NextFunction } from 'express'; -import Joi from 'joi'; +import { Request, Response, NextFunction } from "express"; +import Joi from "joi"; export const validateRequest = (schema: Joi.ObjectSchema) => { return (req: Request, res: Response, next: NextFunction) => { From 8e5e57fe23948ce6182fee95e732c22b02e19fbb Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 09:42:31 +0100 Subject: [PATCH 22/27] feat(error-handling): add global error handler middleware - Added errorHandler middleware to catch and handle errors globally within the Express app - In production mode, the stack trace is hidden to prevent leakage of sensitive information, while in development mode, full details are provided for easier debugging --- src/middleware/errorHandler.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index e69de29..adb36b6 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -0,0 +1,14 @@ +import { Request, Response, NextFunction } from "express"; + +export function errorHandler( + err: Error, + req: Request, + res: Response, + next: NextFunction +) { + console.error(err.stack); + res.status(500).json({ + message: "An unexpected error occurred", + error: process.env.NODE_ENV === "production" ? {} : err, + }); +} From 3f83dbdd713620247f1f6e6f79a348a0848c6501 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 09:44:45 +0100 Subject: [PATCH 23/27] feat(proposals): integrate proposal routes placeholder - Added placeholder routes for proposal-related functionality, to be implemented in future iterations - Allows the structure for proposals to be set up while focusing on vote functionality for now --- src/routes/proposalRoutes.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index 82edae2..e4fe6da 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -33,4 +33,9 @@ router.get("/:id", async (req, res) => { } }); +// Placeholder route for proposals (to be implemented later) +router.get("/", (req, res) => { + res.status(200).json({ message: "Proposal routes placeholder" }); +}); + export default router; From 5074eb96638fa40d62505d80b9c0dd7df73dc1f8 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 09:45:52 +0100 Subject: [PATCH 24/27] refactor(network): extract network configuration logic into utility function - Refactored the logic that determines the network (mainnet/testnet) into a reusable utility function - Reduces duplication and ensures consistency across different parts of the codebase that require network information --- src/utils/stacksUtils.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/stacksUtils.ts b/src/utils/stacksUtils.ts index e69de29..ddadbf4 100644 --- a/src/utils/stacksUtils.ts +++ b/src/utils/stacksUtils.ts @@ -0,0 +1,6 @@ +import { StacksMainnet, StacksTestnet } from '@stacks/network'; +import { CONFIG } from '../config'; + +export function getNetwork() { + return CONFIG.NETWORK === 'mainnet' ? new StacksMainnet() : new StacksTestnet(); +} From 32ef2c8a2a90cec7233e060ecde9c2dc15ef7317 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 10:00:28 +0100 Subject: [PATCH 25/27] security(validation): add input validation and sanitization to vote routes - Introduced input validation using `express-validator` to ensure that proposal IDs and vote options are valid integers and that voter addresses are well-formed - Helps prevent malicious input and strengthens security by ensuring data integrity --- package-lock.json | 36 ++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/routes/voteRoutes.ts | 14 ++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/package-lock.json b/package-lock.json index df776f6..9320ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "dependencies": { "@hirosystems/clarinet-sdk": "^2.3.2", "@stacks/transactions": "^6.12.0", + "@types/express-validator": "^3.0.0", "chokidar-cli": "^3.0.0", "dotenv": "^10.0.0", "express": "^4.17.1", + "express-validator": "^7.2.0", "joi": "^17.13.3", "typescript": "^5.3.3", "vite": "^5.1.4", @@ -187,6 +189,15 @@ "version": "1.0.6", "license": "MIT" }, + "node_modules/@types/express-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/express-validator/-/express-validator-3.0.0.tgz", + "integrity": "sha512-LusnB0YhTXpBT25PXyGPQlK7leE1e41Vezq1hHEUwjfkopM1Pkv2X2Ppxqh9c+w/HZ6Udzki8AJotKNjDTGdkQ==", + "deprecated": "This is a stub types definition for express-validator (https://github.com/ctavan/express-validator). express-validator provides its own type definitions, so you don't need @types/express-validator installed!", + "dependencies": { + "express-validator": "*" + } + }, "node_modules/@types/node": { "version": "18.19.50", "license": "MIT", @@ -941,6 +952,18 @@ "node": ">= 0.10.0" } }, + "node_modules/express-validator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.0.tgz", + "integrity": "sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "license": "MIT", @@ -1238,6 +1261,11 @@ "node": ">=6" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "license": "MIT" @@ -1981,6 +2009,14 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", diff --git a/package.json b/package.json index 5700db4..2ac527d 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "dependencies": { "@hirosystems/clarinet-sdk": "^2.3.2", "@stacks/transactions": "^6.12.0", + "@types/express-validator": "^3.0.0", "chokidar-cli": "^3.0.0", "dotenv": "^10.0.0", "express": "^4.17.1", + "express-validator": "^7.2.0", "joi": "^17.13.3", "typescript": "^5.3.3", "vite": "^5.1.4", diff --git a/src/routes/voteRoutes.ts b/src/routes/voteRoutes.ts index 60ec090..95b3915 100644 --- a/src/routes/voteRoutes.ts +++ b/src/routes/voteRoutes.ts @@ -5,12 +5,26 @@ import { getVoteCount, getTotalVotes, } from "../services/voteService"; +import { body, validationResult } from "express-validator"; const router = express.Router(); router.post("/", async (req, res) => { try { const { proposalId, voteOption } = req.body; + + // Validate input data + await Promise.all([ + body("proposalId").isInt().run(req), + body("voteOption").isInt().run(req), + ]); + + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + // Cast vote and return result const result = await castVote(proposalId, voteOption); res.status(201).json(result); } catch (error) { From 307f8032d49919b4b503198ad789694c84a8987b Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 10:04:13 +0100 Subject: [PATCH 26/27] security(error-handling): prevent stack trace exposure in production mode - Modified the global error handler to only expose error messages in production mode, while stack traces are available in development mode - Protects against potential security vulnerabilities by avoiding stack trace exposure in production environments --- src/middleware/errorHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index adb36b6..ee6f5ba 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -9,6 +9,6 @@ export function errorHandler( console.error(err.stack); res.status(500).json({ message: "An unexpected error occurred", - error: process.env.NODE_ENV === "production" ? {} : err, + error: process.env.NODE_ENV === "production" ? {} : err.message, }); } From c51b6b4b2c8b13ae1fba2076b6d161e76d4019e5 Mon Sep 17 00:00:00 2001 From: nicholas-source Date: Sun, 29 Sep 2024 10:05:42 +0100 Subject: [PATCH 27/27] revert(proposals): remove placeholder proposal route until fully implemented - Removed placeholder proposal route as it was not yet fully functional and created potential confusion in the codebase - This will be reintroduced once the full proposal functionality has been developed --- src/routes/proposalRoutes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/proposalRoutes.ts b/src/routes/proposalRoutes.ts index e4fe6da..7341c8c 100644 --- a/src/routes/proposalRoutes.ts +++ b/src/routes/proposalRoutes.ts @@ -34,8 +34,8 @@ router.get("/:id", async (req, res) => { }); // Placeholder route for proposals (to be implemented later) -router.get("/", (req, res) => { - res.status(200).json({ message: "Proposal routes placeholder" }); -}); +// router.get('/', (req, res) => { +// res.status(200).json({ message: 'Proposal routes placeholder' }); +// }); export default router;