Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions blocklearn-dir/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tests/** linguist-vendored
vitest.config.js linguist-vendored
* text=lf
13 changes: 13 additions & 0 deletions blocklearn-dir/.gitignore
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions blocklearn-dir/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

{
"files.eol": "\n"
}
19 changes: 19 additions & 0 deletions blocklearn-dir/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
19 changes: 19 additions & 0 deletions blocklearn-dir/Clarinet.toml
Original file line number Diff line number Diff line change
@@ -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
87 changes: 87 additions & 0 deletions blocklearn-dir/README.md
Original file line number Diff line number Diff line change
@@ -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.
222 changes: 222 additions & 0 deletions blocklearn-dir/contracts/bl_contract.clar
Original file line number Diff line number Diff line change
@@ -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)
)
)
24 changes: 24 additions & 0 deletions blocklearn-dir/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading