diff --git a/blocklearn-dir/.gitattributes b/blocklearn-dir/.gitattributes new file mode 100644 index 0000000..da6a065 --- /dev/null +++ b/blocklearn-dir/.gitattributes @@ -0,0 +1,3 @@ +tests/** linguist-vendored +vitest.config.js linguist-vendored +* text=lf diff --git a/blocklearn-dir/.gitignore b/blocklearn-dir/.gitignore new file mode 100644 index 0000000..76c2842 --- /dev/null +++ b/blocklearn-dir/.gitignore @@ -0,0 +1,13 @@ + +**/settings/Mainnet.toml +**/settings/Testnet.toml +.cache/** +history.txt + +logs +*.log +npm-debug.log* +coverage +*.info +costs-reports.json +node_modules diff --git a/blocklearn-dir/.vscode/settings.json b/blocklearn-dir/.vscode/settings.json new file mode 100644 index 0000000..3062519 --- /dev/null +++ b/blocklearn-dir/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "files.eol": "\n" +} diff --git a/blocklearn-dir/.vscode/tasks.json b/blocklearn-dir/.vscode/tasks.json new file mode 100644 index 0000000..4dec0ff --- /dev/null +++ b/blocklearn-dir/.vscode/tasks.json @@ -0,0 +1,19 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "check contracts", + "group": "test", + "type": "shell", + "command": "clarinet check" + }, + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [], + "label": "npm test" + } + ] +} diff --git a/blocklearn-dir/Clarinet.toml b/blocklearn-dir/Clarinet.toml new file mode 100644 index 0000000..bb612bb --- /dev/null +++ b/blocklearn-dir/Clarinet.toml @@ -0,0 +1,19 @@ +[project] +name = 'blocklearn-dir' +description = '' +authors = [] +telemetry = true +cache_dir = '.\.cache' +requirements = [] +[contracts.bl_contract] +path = 'contracts/bl_contract.clar' +clarity_version = 2 +epoch = 2.5 +[repl.analysis] +passes = ['check_checker'] + +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/blocklearn-dir/README.md b/blocklearn-dir/README.md new file mode 100644 index 0000000..bf77b67 --- /dev/null +++ b/blocklearn-dir/README.md @@ -0,0 +1,87 @@ +# BlockLearn + +BlockLearn is a decentralized educational platform built on the Stacks blockchain that enables educators to create, monetize, and manage online courses while providing students with verifiable course enrollments and secure access to educational content. + +## Features + +- **Course Management** + - Educators can register courses with customizable pricing + - Support for both one-time purchases and subscription-based models + - Secure content access through blockchain verification + - Custom syllabus URI support for course materials + +- **Revenue Management** + - Automated revenue sharing between educators and platform + - Transparent commission structure + - Direct educator earnings withdrawal + - Real-time balance tracking + +- **Enrollment System** + - Secure student enrollment verification + - Subscription period management + - Enrollment status tracking + - Cancellation support + +- **Administrative Controls** + - Flexible platform commission adjustment + - Secure administrative transfer capability + - Built-in access control mechanisms + +## Smart Contract Functions + +### For Educators + +- `register-course`: Register a new course with pricing and enrollment parameters +- `withdraw-educator-earnings`: Withdraw accumulated teaching revenue +- `get-educator-current-balance`: Check current earnings balance + +### For Students + +- `enroll-in-course`: Enroll in a specific course +- `cancel-enrollment`: Cancel an active course enrollment +- `verify-course-access`: Verify access rights to course content + +### For Administrators + +- `update-platform-commission`: Update the platform's commission rate +- `transfer-platform-administration`: Transfer administrative rights + +## Error Codes + +| Code | Description | +|------|-------------| +| u1 | Unauthorized Access | +| u2 | Invalid Pricing Parameters | +| u3 | Duplicate Enrollment | +| u4 | Course Not Found | +| u5 | Insufficient STX Balance | +| u6 | Enrollment Expired | +| u7 | Invalid Enrollment Duration | +| u8 | Invalid Course ID | +| u9 | Invalid Syllabus URI | +| u10 | Invalid Administrator | + +## Technical Details + +- Built on Stacks blockchain +- Written in Clarity smart contract language +- Uses STX for payments and transactions +- Implements secure principal-based authentication + +## Getting Started + +1. Deploy the smart contract to the Stacks blockchain +2. Set up the initial platform administrator +3. Configure the platform commission rate +4. Begin registering courses and managing enrollments + +## Security Considerations + +- All financial transactions are secured by blockchain technology +- Access control is managed through principal-based authentication +- Revenue distribution is automated and transparent +- Administrative functions are protected by authorization checks + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/blocklearn-dir/contracts/bl_contract.clar b/blocklearn-dir/contracts/bl_contract.clar new file mode 100644 index 0000000..20236c1 --- /dev/null +++ b/blocklearn-dir/contracts/bl_contract.clar @@ -0,0 +1,222 @@ +;; Final Course Management System +;; Production-ready version with full functionality + +;; Error codes +(define-constant ERR-UNAUTHORIZED-ACCESS (err u1)) +(define-constant ERR-INVALID-PRICING-PARAMETERS (err u2)) +(define-constant ERR-DUPLICATE-ENROLLMENT (err u3)) +(define-constant ERR-COURSE-NOT-FOUND (err u4)) +(define-constant ERR-INSUFFICIENT-STX-BALANCE (err u5)) +(define-constant ERR-ENROLLMENT-EXPIRED (err u6)) +(define-constant ERR-INVALID-ENROLLMENT-DURATION (err u7)) +(define-constant ERR-INVALID-COURSE-ID (err u8)) +(define-constant ERR-INVALID-SYLLABUS-URI (err u9)) +(define-constant ERR-INVALID-ADMINISTRATOR (err u10)) + +;; Data variables +(define-data-var platform-administrator principal tx-sender) +(define-data-var platform-commission-rate uint u50) ;; 5% platform fee (base 1000) + +;; Data maps +(define-map course-registry + { course-id: uint } + { + educator: principal, + course-price-stx: uint, + educator-revenue-percentage: uint, + syllabus-uri: (string-utf8 256), + subscription-enabled: bool, + enrollment-period-blocks: uint + } +) + +(define-map student-enrollments + { student: principal, course-id: uint } + { + enrollment-timestamp: uint, + enrollment-end-block: uint, + enrollment-status-active: bool + } +) + +(define-map educator-earnings-ledger + { educator: principal } + { available-balance: uint } +) + +;; Private functions +(define-private (calculate-revenue-distribution (total-price uint)) + (let + ( + (platform-fee-amount (/ (* total-price (var-get platform-commission-rate)) u1000)) + ) + { + platform-commission: platform-fee-amount, + educator-earnings: (- total-price platform-fee-amount) + } + ) +) + +(define-private (execute-stx-transfer (amount uint) (recipient principal)) + (stx-transfer? amount tx-sender recipient) +) + +(define-private (verify-enrollment-status (student-address principal) (course-id uint)) + (match (map-get? student-enrollments { student: student-address, course-id: course-id }) + enrollment-record (and + (get enrollment-status-active enrollment-record) + (<= block-height (get enrollment-end-block enrollment-record)) + ) + false + ) +) + +;; Public functions +(define-public (register-course (course-id uint) + (course-price-stx uint) + (educator-revenue-percentage uint) + (syllabus-uri (string-utf8 256)) + (subscription-enabled bool) + (enrollment-period-blocks uint)) + (begin + (asserts! (> course-id u0) ERR-INVALID-COURSE-ID) + (asserts! (> course-price-stx u0) ERR-INVALID-PRICING-PARAMETERS) + (asserts! (and (>= educator-revenue-percentage u0) (<= educator-revenue-percentage u1000)) ERR-INVALID-PRICING-PARAMETERS) + (asserts! (> (len syllabus-uri) u0) ERR-INVALID-SYLLABUS-URI) + (asserts! (or (not subscription-enabled) (> enrollment-period-blocks u0)) ERR-INVALID-ENROLLMENT-DURATION) + + (map-set course-registry + { course-id: course-id } + { + educator: tx-sender, + course-price-stx: course-price-stx, + educator-revenue-percentage: educator-revenue-percentage, + syllabus-uri: syllabus-uri, + subscription-enabled: subscription-enabled, + enrollment-period-blocks: enrollment-period-blocks + } + ) + (ok true) + ) +) + +(define-public (enroll-in-course (course-id uint)) + (let + ( + (course-details (unwrap! (map-get? course-registry { course-id: course-id }) ERR-COURSE-NOT-FOUND)) + (revenue-distribution (calculate-revenue-distribution (get course-price-stx course-details))) + (educator-address (get educator course-details)) + (current-block-height block-height) + ) + + (asserts! (> course-id u0) ERR-INVALID-COURSE-ID) + (asserts! (not (verify-enrollment-status tx-sender course-id)) ERR-DUPLICATE-ENROLLMENT) + + (try! (execute-stx-transfer (get course-price-stx course-details) (as-contract tx-sender))) + + (map-set educator-earnings-ledger + { educator: educator-address } + { + available-balance: (+ (default-to u0 + (get available-balance (map-get? educator-earnings-ledger { educator: educator-address }))) + (get educator-earnings revenue-distribution)) + } + ) + + (map-set student-enrollments + { student: tx-sender, course-id: course-id } + { + enrollment-timestamp: current-block-height, + enrollment-end-block: (if (get subscription-enabled course-details) + (+ current-block-height (get enrollment-period-blocks course-details)) + u0), + enrollment-status-active: true + } + ) + + (ok true) + ) +) + +(define-public (withdraw-educator-earnings) + (let + ( + (educator-earnings-record (unwrap! (map-get? educator-earnings-ledger { educator: tx-sender }) ERR-COURSE-NOT-FOUND)) + (withdrawal-amount (get available-balance educator-earnings-record)) + ) + + (asserts! (> withdrawal-amount u0) ERR-INSUFFICIENT-STX-BALANCE) + + (map-set educator-earnings-ledger + { educator: tx-sender } + { available-balance: u0 } + ) + + (try! (execute-stx-transfer withdrawal-amount tx-sender)) + (ok true) + ) +) + +(define-public (cancel-enrollment (course-id uint)) + (let + ( + (enrollment-record (unwrap! (map-get? student-enrollments + { student: tx-sender, course-id: course-id }) ERR-COURSE-NOT-FOUND)) + ) + + (asserts! (> course-id u0) ERR-INVALID-COURSE-ID) + (asserts! (get enrollment-status-active enrollment-record) ERR-COURSE-NOT-FOUND) + + (map-set student-enrollments + { student: tx-sender, course-id: course-id } + { + enrollment-timestamp: (get enrollment-timestamp enrollment-record), + enrollment-end-block: block-height, + enrollment-status-active: false + } + ) + (ok true) + ) +) + +;; Read-only functions +(define-read-only (get-course-info (course-id uint)) + (map-get? course-registry { course-id: course-id }) +) + +(define-read-only (get-student-enrollment-info (student principal) (course-id uint)) + (map-get? student-enrollments { student: student, course-id: course-id }) +) + +(define-read-only (get-educator-current-balance (educator principal)) + (default-to u0 (get available-balance (map-get? educator-earnings-ledger { educator: educator }))) +) + +(define-read-only (verify-course-access (student principal) (course-id uint)) + (begin + (asserts! (> course-id u0) ERR-INVALID-COURSE-ID) + (match (map-get? student-enrollments { student: student, course-id: course-id }) + enrollment-record (ok (verify-enrollment-status student course-id)) + ERR-COURSE-NOT-FOUND + ) + ) +) + +;; Administrative functions +(define-public (update-platform-commission (new-commission-rate uint)) + (begin + (asserts! (is-eq tx-sender (var-get platform-administrator)) ERR-UNAUTHORIZED-ACCESS) + (asserts! (<= new-commission-rate u1000) ERR-INVALID-PRICING-PARAMETERS) + (var-set platform-commission-rate new-commission-rate) + (ok true) + ) +) + +(define-public (transfer-platform-administration (new-administrator principal)) + (begin + (asserts! (is-eq tx-sender (var-get platform-administrator)) ERR-UNAUTHORIZED-ACCESS) + (asserts! (not (is-eq new-administrator 'SP000000000000000000002Q6VF78)) ERR-INVALID-ADMINISTRATOR) + (var-set platform-administrator new-administrator) + (ok true) + ) +) \ No newline at end of file diff --git a/blocklearn-dir/package.json b/blocklearn-dir/package.json new file mode 100644 index 0000000..644a6c2 --- /dev/null +++ b/blocklearn-dir/package.json @@ -0,0 +1,24 @@ + +{ + "name": "blocklearn-dir-tests", + "version": "1.0.0", + "description": "Run unit tests on this project.", + "type": "module", + "private": true, + "scripts": { + "test": "vitest run", + "test:report": "vitest run -- --coverage --costs", + "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\"" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@hirosystems/clarinet-sdk": "^2.3.2", + "@stacks/transactions": "^6.12.0", + "chokidar-cli": "^3.0.0", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1", + "vitest-environment-clarinet": "^2.0.0" + } +} diff --git a/blocklearn-dir/settings/Devnet.toml b/blocklearn-dir/settings/Devnet.toml new file mode 100644 index 0000000..54a2c35 --- /dev/null +++ b/blocklearn-dir/settings/Devnet.toml @@ -0,0 +1,157 @@ +[network] +name = "devnet" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +balance = 100_000_000_000_000 +# secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 +# stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +# btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + +[accounts.wallet_1] +mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" +balance = 100_000_000_000_000 +# secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801 +# stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 +# btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC + +[accounts.wallet_2] +mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital" +balance = 100_000_000_000_000 +# secret_key: 530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101 +# stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG +# btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + +[accounts.wallet_3] +mnemonic = "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high" +balance = 100_000_000_000_000 +# secret_key: d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901 +# stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC +# btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + +[accounts.wallet_4] +mnemonic = "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin" +balance = 100_000_000_000_000 +# secret_key: f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 +# stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND +# btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8 + +[accounts.wallet_5] +mnemonic = "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase" +balance = 100_000_000_000_000 +# secret_key: 3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 +# stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB +# btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx + +[accounts.wallet_6] +mnemonic = "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy" +balance = 100_000_000_000_000 +# secret_key: 7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 +# stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 +# btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt + +[accounts.wallet_7] +mnemonic = "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow" +balance = 100_000_000_000_000 +# secret_key: b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401 +# stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ +# btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7 + +[accounts.wallet_8] +mnemonic = "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune" +balance = 100_000_000_000_000 +# secret_key: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 +# stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +# btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw + +[accounts.faucet] +mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +balance = 100_000_000_000_000 +# secret_key: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801 +# stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 +# btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + +[devnet] +disable_stacks_explorer = false +disable_stacks_api = false +# disable_postgres = false +# disable_subnet_api = false +# disable_bitcoin_explorer = true +# working_dir = "tmp/devnet" +# stacks_node_events_observers = ["host.docker.internal:8002"] +# miner_mnemonic = "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce" +# miner_derivation_path = "m/44'/5757'/0'/0/0" +# faucet_mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" +# faucet_derivation_path = "m/44'/5757'/0'/0/0" +# stacker_mnemonic = "empty lens any direct brother then drop fury rule pole win claim scissors list rescue horn rent inform relief jump sword weekend half legend" +# stacker_derivation_path = "m/44'/5757'/0'/0/0" +# orchestrator_port = 20445 +# bitcoin_node_p2p_port = 18444 +# bitcoin_node_rpc_port = 18443 +# bitcoin_node_username = "devnet" +# bitcoin_node_password = "devnet" +# bitcoin_controller_block_time = 30_000 +# stacks_node_rpc_port = 20443 +# stacks_node_p2p_port = 20444 +# stacks_api_port = 3999 +# stacks_api_events_port = 3700 +# bitcoin_explorer_port = 8001 +# stacks_explorer_port = 8000 +# postgres_port = 5432 +# postgres_username = "postgres" +# postgres_password = "postgres" +# postgres_database = "postgres" +# bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:26.0" +# stacks_node_image_url = "quay.io/hirosystems/stacks-node:devnet-3.0" +# stacks_signer_image_url = "quay.io/hirosystems/stacks-signer:devnet-3.0" +# stacks_api_image_url = "hirosystems/stacks-blockchain-api:master" +# stacks_explorer_image_url = "hirosystems/explorer:latest" +# bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet" +# postgres_image_url = "postgres:alpine" +# enable_subnet_node = true +# subnet_node_image_url = "hirosystems/stacks-subnets:0.8.1" +# subnet_leader_mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" +# subnet_leader_derivation_path = "m/44'/5757'/0'/0/0" +# subnet_contract_id = "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1" +# subnet_node_rpc_port = 30443 +# subnet_node_p2p_port = 30444 +# subnet_events_ingestion_port = 30445 +# subnet_node_events_observers = ["host.docker.internal:8002"] +# subnet_api_image_url = "hirosystems/stacks-blockchain-api:master" +# subnet_api_postgres_database = "subnet_api" + +# epoch_2_0 = 100 +# epoch_2_05 = 100 +# epoch_2_1 = 101 +# epoch_2_2 = 102 +# epoch_2_3 = 103 +# epoch_2_4 = 104 +# epoch_2_5 = 108 +# epoch_3_0 = 142 + +# Send some stacking orders +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_1" +slots = 2 +btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_2" +slots = 2 +btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + +[[devnet.pox_stacking_orders]] +start_at_cycle = 1 +duration = 10 +auto_extend = true +wallet = "wallet_3" +slots = 2 +btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" + diff --git a/blocklearn-dir/tests/bl_contract.test.ts b/blocklearn-dir/tests/bl_contract.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/blocklearn-dir/tests/bl_contract.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); + // }); +}); diff --git a/blocklearn-dir/tsconfig.json b/blocklearn-dir/tsconfig.json new file mode 100644 index 0000000..1bdaf36 --- /dev/null +++ b/blocklearn-dir/tsconfig.json @@ -0,0 +1,26 @@ + +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext"], + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src", + "tests" + ] +} diff --git a/blocklearn-dir/vitest.config.js b/blocklearn-dir/vitest.config.js new file mode 100644 index 0000000..c6a8506 --- /dev/null +++ b/blocklearn-dir/vitest.config.js @@ -0,0 +1,42 @@ + +/// + +import { defineConfig } from "vite"; +import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest"; + +/* + In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet. + + The `vitest-environment-clarinet` will initialise the clarinet-sdk + and make the `simnet` object available globally in the test files. + + `vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things: + - run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports. + - load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`) + + The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --` + - vitest run -- --manifest ./Clarinet.toml # pass a custom path + - vitest run -- --coverage --costs # collect coverage and cost reports +*/ + +export default defineConfig({ + test: { + environment: "clarinet", // use vitest-environment-clarinet + pool: "forks", + poolOptions: { + threads: { singleThread: true }, + forks: { singleFork: true }, + }, + setupFiles: [ + vitestSetupFilePath, + // custom setup files can be added here + ], + environmentOptions: { + clarinet: { + ...getClarinetVitestsArgv(), + // add or override options + }, + }, + }, +}); +