From 19979b4971944a4f015c3c774584bbe690784260 Mon Sep 17 00:00:00 2001 From: samuel1-ona Date: Fri, 12 Dec 2025 10:47:27 +0100 Subject: [PATCH 1/3] Implemented withdraw function for protocol fees and readme updates --- README.md | 151 +++++++++++++++++++++++++++++++++++++++++++- contracts/roxy.clar | 106 ++++++++++++++++++++----------- 2 files changed, 220 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 97b70f8..009f767 100644 --- a/README.md +++ b/README.md @@ -1 +1,150 @@ -## ROXY ON CLARINET \ No newline at end of file +# Roxy - Bitcoin L2 Prediction Market Game + +## Overview + +Roxy is a decentralized prediction market platform built on Bitcoin Layer 2 (Stacks blockchain) that enables users to make predictions on various outcomes, accumulate points through successful predictions, and participate in a peer-to-peer marketplace for trading points. The platform combines individual prediction capabilities with collaborative guild-based prediction systems, creating a competitive environment with comprehensive leaderboards. + + +![Roxy Logo](https://github.com/user-attachments/assets/f57fe362-4e7c-40d6-977d-cd521fec1452) + +## Features + +### Prediction Markets + +- **Binary Event System**: Create and participate in YES/NO prediction events covering sports, price movements, and other outcomes +- **Proportional Rewards**: Winners receive rewards proportional to their stake in the winning pool +- **Admin-Controlled Events**: Secure event creation and resolution managed by contract administrators +- **Multi-Event Support**: Simultaneous participation across multiple prediction events + +### Collaborative Guild System + +- **Guild Creation**: Users can form prediction guilds to pool resources and strategies +- **Shared Staking**: Guild members contribute points to a collective pool for larger prediction stakes +- **Collective Rewards**: Winnings are distributed among guild members based on contributions +- **Guild Leaderboards**: Track and compare guild performance metrics across the platform + +### Point Marketplace + +- **Point Trading**: Buy and sell earned points using STX tokens +- **Partial Purchases**: Support for buying portions of listed point packages +- **Protocol Fees**: 2% transaction fee automatically collected by the protocol +- **Listing Requirements**: + - Minimum 10,000 earned points required to create listings + - 10 STX listing fee per marketplace listing +- **Secure Transactions**: All trades executed on-chain with automatic point transfers + +### Leaderboard & Statistics + +- **Individual Metrics**: Track personal performance including total predictions, wins, losses, and win rate +- **Guild Rankings**: Compare guild performance across multiple metrics +- **Points Tracking**: Monitor total points earned and current balances +- **Real-time Updates**: On-chain statistics updated with each transaction + +### Points & Rewards System + +- **Welcome Bonus**: New users receive 1,000 starting points upon registration +- **Earning Mechanism**: Accumulate points by correctly predicting event outcomes +- **Reward Distribution**: Proportional payout system ensures fair distribution based on stake size +- **Earned Points Tracking**: Separate tracking of earned points for marketplace eligibility + +## Architecture + +### Data Storage + +#### User Management +- **`user-points`**: Total point balance for each registered user +- **`earned-points`**: Points accumulated from winning predictions (used for marketplace selling threshold) +- **`user-names`**: Username registry for user identification +- **`user-stats`**: Comprehensive statistics including prediction count, wins, losses, win rate, and total points earned + +#### Event Management +- **`events`**: Complete event registry containing pool sizes, status (open/closed/resolved), winner information, and metadata +- **`yes-stakes`**: Individual user stakes on YES outcomes per event +- **`no-stakes`**: Individual user stakes on NO outcomes per event + +#### Guild System +- **`guilds`**: Guild information including creator, name, total pooled points, and member count +- **`guild-members`**: Membership registry tracking which users belong to which guilds +- **`guild-deposits`**: Individual user contributions to guild point pools +- **`guild-yes-stakes`**: Guild collective stakes on YES outcomes per event +- **`guild-no-stakes`**: Guild collective stakes on NO outcomes per event +- **`guild-stats`**: Guild-level performance metrics for leaderboard rankings + +#### Marketplace +- **`listings`**: Active and inactive point sale listings with seller information, point amounts, STX prices, and status +- **`protocol-treasury`**: Accumulated protocol fees from marketplace transactions and listing fees + +#### Transaction Logging +- **`transaction-logs`**: Comprehensive on-chain event log for frontend integration, tracking all major actions (registrations, staking, claims, marketplace transactions, etc.) + +## Key Functions + +### User Functions +- `register(username)`: Register a new user and receive starting points +- `stake-yes(event-id, amount)`: Place a YES prediction stake +- `stake-no(event-id, amount)`: Place a NO prediction stake +- `claim(event-id)`: Claim rewards from resolved events + +### Marketplace Functions +- `create-listing(points, price-stx)`: List points for sale (requires 10,000+ earned points) +- `buy-listing(listing-id, points-to-buy)`: Purchase points from marketplace +- `cancel-listing(listing-id)`: Cancel an active listing + +### Guild Functions +- `create-guild(guild-id, name)`: Create a new prediction guild +- `join-guild(guild-id)`: Join an existing guild +- `deposit-to-guild(guild-id, amount)`: Contribute points to guild pool +- `guild-stake-yes(guild-id, event-id, amount)`: Place guild YES stake +- `guild-stake-no(guild-id, event-id, amount)`: Place guild NO stake +- `guild-claim(guild-id, event-id)`: Claim guild rewards + +### Admin Functions +- `create-event(event-id, metadata)`: Create new prediction events +- `resolve-event(event-id, winner)`: Resolve events and set winners +- `withdraw-protocol-fees(amount)`: Withdraw accumulated protocol fees + +### Read-Only Functions +- `get-user-points(user)`: Query user point balance +- `get-earned-points(user)`: Query earned points (for marketplace eligibility) +- `can-sell(user)`: Check if user can create marketplace listings +- `get-event(event-id)`: Get event details and status +- `get-listing(listing-id)`: Get marketplace listing information +- `get-protocol-treasury()`: Query protocol treasury balance +- `get-user-stats(user)`: Get user leaderboard statistics +- `get-guild-stats(guild-id)`: Get guild leaderboard statistics + +## Constants + +- **Starting Points**: 1,000 points for new users +- **Minimum Earned for Selling**: 10,000 earned points required to create marketplace listings +- **Listing Fee**: 10 STX per marketplace listing +- **Protocol Fee**: 2% (200 basis points) on all marketplace transactions + +## Getting Started + +### Prerequisites +- Stacks wallet (Hiro Wallet recommended) +- STX tokens for transaction fees and marketplace purchases +- Clarinet development environment (for local testing) + + +### Usage + +1. **Register**: Call `register(username)` to create an account and receive 1,000 starting points +2. **Participate**: Stake points on prediction events using `stake-yes` or `stake-no` +3. **Claim Rewards**: After events are resolved, use `claim` to collect your winnings +4. **Trade Points**: Once you've earned 10,000+ points, create listings on the marketplace +5. **Join Guilds**: Form or join guilds for collaborative predictions and shared rewards + +## Protocol Fees + +The protocol collects fees to support development, rewards, and governance: +- **Marketplace Fee**: 2% of each point sale transaction +- **Listing Fee**: 10 STX per marketplace listing creation +- **Treasury Management**: Admin can withdraw accumulated fees via `withdraw-protocol-fees` + + + + + + diff --git a/contracts/roxy.clar b/contracts/roxy.clar index 46e4ca6..13dc932 100644 --- a/contracts/roxy.clar +++ b/contracts/roxy.clar @@ -41,6 +41,7 @@ (define-constant ERR-NOT-A-MEMBER (err u22)) (define-constant ERR-HAS-DEPOSITS (err u23)) (define-constant ERR-INSUFFICIENT-DEPOSITS (err u24)) +(define-constant ERR-INSUFFICIENT-TREASURY (err u25)) ;; data vars (define-data-var admin principal tx-sender) @@ -970,6 +971,8 @@ (asserts! (>= current-points points) ERR-INSUFFICIENT-POINTS) ;; Insufficient points ;; Transfer STX listing fee to contract (try! (stx-transfer? LISTING_FEE seller (as-contract tx-sender))) + ;; Add listing fee to protocol treasury + (var-set protocol-treasury (+ (var-get protocol-treasury) LISTING_FEE)) ;; Lock seller's points by deducting them (map-set user-points seller (- current-points points)) ;; Create listing @@ -1195,7 +1198,66 @@ ) ;; ============================================================================ -;; 10. create-guild (guild-id, name) +;; 10. withdraw-protocol-fees (amount) +;; ============================================================================ +;; Purpose: Withdraw protocol fees from the treasury (admin only). +;; +;; Details: +;; - Verifies caller is admin (error u2) +;; - Validates amount > 0 (error u4) +;; - Checks treasury has sufficient balance (error u25) +;; - Transfers STX from contract to admin +;; - Updates protocol-treasury balance +;; - Returns (ok true) on success +;; +;; Use case: Admin withdraws accumulated protocol fees for dev funding, rewards, or governance. +;; +;; Parameters: +;; - amount: uint - Amount in micro-STX to withdraw +;; +;; Returns: +;; - (ok true) on success +;; - ERR-NOT-ADMIN if caller is not admin +;; - ERR-INVALID-AMOUNT if amount <= 0 +;; - ERR-INSUFFICIENT-TREASURY if treasury balance is insufficient +;; ============================================================================ +(define-public (withdraw-protocol-fees (amount uint)) + (let ((caller tx-sender) + (admin-principal (var-get admin))) + (asserts! (is-eq caller admin-principal) ERR-NOT-ADMIN) ;; Only admin can withdraw + (asserts! (> amount u0) ERR-INVALID-AMOUNT) ;; Amount must be greater than 0 + (let ((treasury-balance (var-get protocol-treasury))) + (asserts! (>= treasury-balance amount) ERR-INSUFFICIENT-TREASURY) ;; Insufficient treasury balance + ;; Update protocol treasury balance + (var-set protocol-treasury (- treasury-balance amount)) + ;; Transfer STX from contract to admin + (try! (stx-transfer? amount (as-contract tx-sender) admin-principal)) + ;; Emit event + (print { + event: "protocol-fees-withdrawn", + admin: admin-principal, + amount: amount, + remaining-balance: (- treasury-balance amount) + }) + ;; Log transaction + (let ((log-id (var-get next-log-id))) + (map-set transaction-logs log-id { + action: "withdraw-protocol-fees", + user: admin-principal, + event-id: none, + listing-id: none, + amount: (some amount), + metadata: "protocol-fees-withdrawn" + }) + (var-set next-log-id (+ log-id u1)) + ) + (ok true) + ) + ) +) + +;; ============================================================================ +;; 11. create-guild (guild-id, name) ;; ============================================================================ ;; Purpose: Create a new guild for collaborative predictions. ;; @@ -1242,7 +1304,7 @@ ) ;; ============================================================================ -;; 11. join-guild (guild-id) +;; 12. join-guild (guild-id) ;; ============================================================================ ;; Purpose: Join an existing guild. ;; @@ -1294,7 +1356,7 @@ ) ;; ============================================================================ -;; 12. leave-guild (guild-id) +;; 13. leave-guild (guild-id) ;; ============================================================================ ;; Purpose: Leave a guild (can only withdraw own deposits first). ;; @@ -1368,7 +1430,7 @@ ) ;; ============================================================================ -;; 13. deposit-to-guild (guild-id, amount) +;; 14. deposit-to-guild (guild-id, amount) ;; ============================================================================ ;; Purpose: Deposit points to guild pool for collaborative predictions. ;; @@ -1449,7 +1511,7 @@ ) ;; ============================================================================ -;; 14. withdraw-from-guild (guild-id, amount) +;; 15. withdraw-from-guild (guild-id, amount) ;; ============================================================================ ;; Purpose: Withdraw own deposits from guild pool. ;; @@ -1524,7 +1586,7 @@ ) ;; ============================================================================ -;; 15. guild-stake-yes (guild-id, event-id, amount) +;; 16. guild-stake-yes (guild-id, event-id, amount) ;; ============================================================================ ;; Purpose: Guild stakes points on YES outcome of an event. ;; @@ -1634,7 +1696,7 @@ ) ;; ============================================================================ -;; 16. guild-stake-no (guild-id, event-id, amount) +;; 17. guild-stake-no (guild-id, event-id, amount) ;; ============================================================================ ;; Purpose: Guild stakes points on NO outcome of an event. ;; @@ -1737,7 +1799,7 @@ ) ;; ============================================================================ -;; 17. guild-claim (guild-id, event-id) +;; 18. guild-claim (guild-id, event-id) ;; ============================================================================ ;; Purpose: Claim rewards for guild from a resolved event if guild won. ;; @@ -2378,31 +2440,3 @@ ) ) -;; ============================================================================ -;; ERROR CODES REFERENCE -;; ============================================================================ -;; u1: User already registered -;; u2: Not admin -;; u3: Event ID already exists -;; u4: Amount/price must be > 0 -;; u5: Event not open -;; u6: Insufficient points -;; u7: User not registered -;; u8: Event not found -;; u9: Event must be open to resolve -;; u10: Event must be resolved -;; u11: No winners (pool empty) -;; u12: No stake found -;; u13: Winner not set -;; u14: Must have earned >= 10,000 points -;; u15: Listing not active -;; u16: Listing not found -;; u17: Only seller can cancel -;; u18: Not enough points available in listing -;; u19: Guild ID already exists -;; u20: Guild not found -;; u21: Already a guild member -;; u22: Not a guild member -;; u23: Has deposits (must withdraw first) -;; u24: Insufficient deposits -;; ============================================================================ From b313073fe6085de7cf450a976e446b43da9371d7 Mon Sep 17 00:00:00 2001 From: samuel1-ona Date: Fri, 12 Dec 2025 12:42:14 +0100 Subject: [PATCH 2/3] Set up Docker Image (ghcr) , fixed username uniqueness --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++ contracts/roxy.clar | 59 ++++++++++++++++++++++++---------------- vitest.config.js | 12 ++++---- 3 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9a2fd2e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + sanity-checks: + runs-on: ubuntu-latest + container: ghcr.io/stx-labs/clarinet:latest + steps: + - uses: actions/checkout@v4 + + - name: Check Clarity contracts check + run: clarinet check --use-on-disk-deployment-plan + + - name: Check Clarity contracts format + run: clarinet fmt --check + + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Run tests with coverage and costs + run: npm run test:report diff --git a/contracts/roxy.clar b/contracts/roxy.clar index 13dc932..fe3ac05 100644 --- a/contracts/roxy.clar +++ b/contracts/roxy.clar @@ -42,6 +42,7 @@ (define-constant ERR-HAS-DEPOSITS (err u23)) (define-constant ERR-INSUFFICIENT-DEPOSITS (err u24)) (define-constant ERR-INSUFFICIENT-TREASURY (err u25)) +(define-constant ERR-USERNAME-TAKEN (err u26)) ;; data vars (define-data-var admin principal tx-sender) @@ -58,6 +59,7 @@ (define-map user-points principal uint) (define-map earned-points principal uint) ;; Points earned from predictions (used for selling threshold) (define-map user-names principal (string-ascii 50)) ;; User names +(define-map usernames (string-ascii 50) principal) ;; Track username for uniqueness (username -> user) ;; Prediction Event Registry (define-map events uint { @@ -135,48 +137,57 @@ ;; Details: ;; - Takes a username (up to 50 ASCII characters) ;; - Checks if the user is already registered (error u1 if yes) +;; - Checks if the username is already taken (error u26 if yes) ;; - Grants 1,000 starting points (non-sellable) ;; - Sets earned-points to 0 (starting points don't count toward selling threshold) -;; - Stores the username +;; - Stores the username and tracks it for uniqueness ;; - Returns (ok true) on success ;; ;; Use case: First-time user onboarding. ;; ;; Parameters: -;; - username: (string-ascii 50) - User's chosen username +;; - username: (string-ascii 50) - User's chosen username (must be unique) ;; ;; Returns: ;; - (ok true) on success ;; - ERR-USER-ALREADY-REGISTERED if user already registered +;; - ERR-USERNAME-TAKEN if username is already taken by another user ;; ============================================================================ (define-public (register (username (string-ascii 50))) (let ((user tx-sender)) (match (map-get? user-points user) existing ERR-USER-ALREADY-REGISTERED ;; User already registered (begin - (map-set user-points user STARTING_POINTS) - (map-set earned-points user u0) ;; Starting points don't count as earned - (map-set user-names user username) - ;; Emit event - (print { - event: "user-registered", - user: user, - username: username, - points: STARTING_POINTS - }) - ;; Log transaction - (let ((log-id (var-get next-log-id))) - (map-set transaction-logs log-id { - action: "register", - user: user, - event-id: none, - listing-id: none, - amount: (some STARTING_POINTS), - metadata: username - }) - (var-set next-log-id (+ log-id u1)) + ;; Track username for uniqueness + (match (map-get? usernames username) + existing-user ERR-USERNAME-TAKEN ;; Username already taken + (begin + (map-set user-points user STARTING_POINTS) + (map-set earned-points user u0) ;; Starting points don't count as earned + (map-set user-names user username) + (map-set usernames username user) ;; Store username -> user mapping for uniqueness + ;; Emit event + (print { + event: "user-registered", + user: user, + username: username, + points: STARTING_POINTS + }) + ;; Log transaction + (let ((log-id (var-get next-log-id))) + (map-set transaction-logs log-id { + action: "register", + user: user, + event-id: none, + listing-id: none, + amount: (some STARTING_POINTS), + metadata: username + }) + (var-set next-log-id (+ log-id u1)) + ) + (ok true) + ) ) - (ok true) ) ) ) diff --git a/vitest.config.js b/vitest.config.js index 5a71683..5ac5a3a 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,7 +1,7 @@ /// -import { defineConfig } from "vite"; +import { defineConfig } from "vitest/config"; import { vitestSetupFilePath, getClarinetVitestsArgv } from "@stacks/clarinet-sdk/vitest"; /* @@ -21,12 +21,12 @@ import { vitestSetupFilePath, getClarinetVitestsArgv } from "@stacks/clarinet-sd export default defineConfig({ test: { - environment: "clarinet", // use vitest-environment-clarinet + // use vitest-environment-clarinet + environment: "clarinet", pool: "forks", - poolOptions: { - threads: { singleThread: true }, - forks: { singleFork: true }, - }, + // clarinet handles test isolation by resetting the simnet between tests + isolate: false, + maxWorkers: 1, setupFiles: [ vitestSetupFilePath, // custom setup files can be added here From 0293435af910acf27edea28f7dc76d1ae909b3a6 Mon Sep 17 00:00:00 2001 From: samuel1-ona Date: Fri, 12 Dec 2025 18:54:51 +0100 Subject: [PATCH 3/3] fixed github workflow --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a2fd2e..30130fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,7 @@ jobs: - name: Check Clarity contracts check run: clarinet check --use-on-disk-deployment-plan - - name: Check Clarity contracts format - run: clarinet fmt --check + tests: runs-on: ubuntu-latest