Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
158e3de
feat(pools): add pool registry contract with Liquid, Balanced, and Ma…
thewealthyplace Feb 23, 2026
44ce8f5
feat(pools): add deposit/withdraw contract with lockup enforcement pe…
thewealthyplace Feb 23, 2026
2eb8e4e
feat(tokens): add lbSTX and bbSTX SIP-010 liquid staking tokens for P…
thewealthyplace Feb 23, 2026
23b51e3
feat(tokens): add mbSTX SIP-010 maxi staking token for Pool 3 with 12…
thewealthyplace Feb 23, 2026
6f8c3a9
feat(rewards): add epoch-based reward tracking and distribution contr…
thewealthyplace Feb 23, 2026
83f047e
chore(config): add Clarinet.toml registering all pool-tier contracts
thewealthyplace Feb 23, 2026
891f060
test(registry): add Clarinet test suite for pool registry – all 3 tie…
thewealthyplace Feb 23, 2026
a0c1381
test(deposits): add deposit/withdraw lockup enforcement tests for all…
thewealthyplace Feb 23, 2026
5652924
feat(frontend): add pool tier constants with APY indicators and contr…
thewealthyplace Feb 23, 2026
f043024
feat(frontend): add usePoolTiers and useUserPosition React hooks for …
thewealthyplace Feb 23, 2026
e86d1bc
feat(frontend): add PoolTierCard and PoolSelector components with tie…
thewealthyplace Feb 23, 2026
4b3a1af
feat(frontend): add DepositForm component with wallet signing and loc…
thewealthyplace Feb 23, 2026
b6f9c61
chore(scripts): add testnet deployment script for all pool-tier contr…
thewealthyplace Feb 23, 2026
7e5b929
docs: add multi-pool-tiers documentation with contract flow diagrams …
thewealthyplace Feb 23, 2026
8f02647
ci: add GitHub Actions workflow for Clarinet and TypeScript pool tests
thewealthyplace Feb 23, 2026
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
57 changes: 57 additions & 0 deletions .github/workflows/pool-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Pool Contract Tests

on:
push:
branches: ["**"]
pull_request:
branches: [main]

jobs:
clarinet-test:
name: Clarinet Unit Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Clarinet
run: |
curl -L https://github.com/hirosystems/clarinet/releases/latest/download/clarinet-linux-x64-glibc.tar.gz \
| tar -xz -C /usr/local/bin

- name: Run Clarinet checks
run: clarinet check

- name: Run contract tests
run: clarinet test --coverage

- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: coverage.lcov
fail_ci_if_error: false

typescript-test:
name: TypeScript Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Run TypeScript tests
working-directory: frontend
run: npm test -- --reporter=verbose

- name: Type check
working-directory: frontend
run: npx tsc --noEmit
37 changes: 37 additions & 0 deletions Clarinet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[project]
name = "bitstake"
requirements = []
authors = []
telemetry = false

[contracts.bitstake-pool-registry]
path = "contracts/bitstake-pool-registry.clar"
clarity_version = 2
epoch = "2.5"

[contracts.bitstake-pool-deposits]
path = "contracts/bitstake-pool-deposits.clar"
clarity_version = 2
epoch = "2.5"

[contracts.bitstake-lbstx]
path = "contracts/bitstake-lbstx.clar"
clarity_version = 2
epoch = "2.5"

[contracts.bitstake-bbstx]
path = "contracts/bitstake-bbstx.clar"
clarity_version = 2
epoch = "2.5"

[contracts.bitstake-mbstx]
path = "contracts/bitstake-mbstx.clar"
clarity_version = 2
epoch = "2.5"

[contracts.bitstake-rewards]
path = "contracts/bitstake-rewards.clar"
clarity_version = 2
epoch = "2.5"

[[deployment_plan.genesis]]
42 changes: 42 additions & 0 deletions contracts/bitstake-bbstx.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
;; bitstake-bbstx.clar
;; bbSTX — Balanced Pool liquid staking token (Pool 2, 3-cycle lockup)
;; SIP-010 compliant

(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-fungible-token bbstx)

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-NOT-SENDER (err u101))
(define-constant ERR-ZERO (err u103))

(define-data-var token-name (string-ascii 32) "Balanced bitstake STX")
(define-data-var token-symbol (string-ascii 10) "bbSTX")
(define-data-var token-decimals uint u6)
(define-data-var token-uri (optional (string-utf8 256)) none)

(define-read-only (get-name) (ok (var-get token-name)))
(define-read-only (get-symbol) (ok (var-get token-symbol)))
(define-read-only (get-decimals) (ok (var-get token-decimals)))
(define-read-only (get-balance (account principal)) (ok (ft-get-balance bbstx account)))
(define-read-only (get-total-supply) (ok (ft-get-supply bbstx)))
(define-read-only (get-token-uri) (ok (var-get token-uri)))

(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(begin
(asserts! (is-eq tx-sender sender) ERR-NOT-SENDER)
(asserts! (> amount u0) ERR-ZERO)
(try! (ft-transfer? bbstx amount sender recipient))
(match memo m (print m) 0x)
(ok true)))

(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
(ft-mint? bbstx amount recipient)))

(define-public (burn (amount uint) (owner principal))
(begin
(asserts! (is-eq tx-sender owner) ERR-NOT-SENDER)
(ft-burn? bbstx amount owner)))
42 changes: 42 additions & 0 deletions contracts/bitstake-lbstx.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
;; bitstake-lbstx.clar
;; lbSTX — Liquid Pool liquid staking token (Pool 1, 1-cycle lockup)
;; SIP-010 compliant

(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-fungible-token lbstx)

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-NOT-SENDER (err u101))
(define-constant ERR-ZERO (err u103))

(define-data-var token-name (string-ascii 32) "Liquid bitstake STX")
(define-data-var token-symbol (string-ascii 10) "lbSTX")
(define-data-var token-decimals uint u6)
(define-data-var token-uri (optional (string-utf8 256)) none)

(define-read-only (get-name) (ok (var-get token-name)))
(define-read-only (get-symbol) (ok (var-get token-symbol)))
(define-read-only (get-decimals) (ok (var-get token-decimals)))
(define-read-only (get-balance (account principal)) (ok (ft-get-balance lbstx account)))
(define-read-only (get-total-supply) (ok (ft-get-supply lbstx)))
(define-read-only (get-token-uri) (ok (var-get token-uri)))

(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(begin
(asserts! (is-eq tx-sender sender) ERR-NOT-SENDER)
(asserts! (> amount u0) ERR-ZERO)
(try! (ft-transfer? lbstx amount sender recipient))
(match memo m (print m) 0x)
(ok true)))

(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
(ft-mint? lbstx amount recipient)))

(define-public (burn (amount uint) (owner principal))
(begin
(asserts! (is-eq tx-sender owner) ERR-NOT-SENDER)
(ft-burn? lbstx amount owner)))
48 changes: 48 additions & 0 deletions contracts/bitstake-mbstx.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
;; bitstake-mbstx.clar
;; mbSTX — Maxi Pool liquid staking token (Pool 3, 12-cycle lockup)
;; SIP-010 compliant

(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-fungible-token mbstx)

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-NOT-SENDER (err u101))
(define-constant ERR-ZERO (err u103))

(define-data-var token-name (string-ascii 32) "Maxi bitstake STX")
(define-data-var token-symbol (string-ascii 10) "mbSTX")
(define-data-var token-decimals uint u6)
(define-data-var token-uri (optional (string-utf8 256)) none)

(define-read-only (get-name) (ok (var-get token-name)))
(define-read-only (get-symbol) (ok (var-get token-symbol)))
(define-read-only (get-decimals) (ok (var-get token-decimals)))
(define-read-only (get-balance (account principal)) (ok (ft-get-balance mbstx account)))
(define-read-only (get-total-supply) (ok (ft-get-supply mbstx)))
(define-read-only (get-token-uri) (ok (var-get token-uri)))

(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(begin
(asserts! (is-eq tx-sender sender) ERR-NOT-SENDER)
(asserts! (> amount u0) ERR-ZERO)
(try! (ft-transfer? mbstx amount sender recipient))
(match memo m (print m) 0x)
(ok true)))

(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
(ft-mint? mbstx amount recipient)))

(define-public (burn (amount uint) (owner principal))
(begin
(asserts! (is-eq tx-sender owner) ERR-NOT-SENDER)
(ft-burn? mbstx amount owner)))

(define-public (set-token-uri (new-uri (optional (string-utf8 256))))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
(var-set token-uri new-uri)
(ok true)))
106 changes: 106 additions & 0 deletions contracts/bitstake-pool-deposits.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
;; bitstake-pool-deposits.clar
;; Handles deposits and withdrawals for each pool tier

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-POOL-INACTIVE (err u102))
(define-constant ERR-BELOW-MIN (err u103))
(define-constant ERR-NO-POSITION (err u105))
(define-constant ERR-LOCKED (err u106))

;; (pool-id, depositor) -> position
(define-map positions
{ pool-id: uint, depositor: principal }
{
amount: uint,
deposited-at: uint,
unlock-block: uint
}
)

;; Track total deposited per user across all pools
(define-map user-total-stacked principal uint)

;; ── Deposit ───────────────────────────────────────────────────────────

(define-public (deposit-to-pool (pool-id uint) (amount uint))
(let (
(pool (unwrap! (contract-call? .bitstake-pool-registry get-pool pool-id) (err u101)))
(p-data (unwrap! pool (err u101)))
(min-dep (get min-deposit p-data))
(cycles (get lockup-cycles p-data))
)
(asserts! (get active p-data) ERR-POOL-INACTIVE)
(asserts! (>= amount min-dep) ERR-BELOW-MIN)

;; Transfer STX to contract
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

;; Record position (add to existing if any)
(let (
(key { pool-id: pool-id, depositor: tx-sender })
(existing (map-get? positions key))
(unlock (+ block-height (* cycles u2100))) ;; ~2100 blocks per cycle
)
(match existing
pos (map-set positions key {
amount: (+ (get amount pos) amount),
deposited-at: block-height,
unlock-block: unlock
})
(map-set positions key {
amount: amount,
deposited-at: block-height,
unlock-block: unlock
})
)
)

;; Update registry total
(try! (contract-call? .bitstake-pool-registry add-to-total pool-id amount))

(print { event: "deposit", pool-id: pool-id, depositor: tx-sender, amount: amount })
(ok true)
)
)

;; ── Withdraw ──────────────────────────────────────────────────────────

(define-public (withdraw-from-pool (pool-id uint))
(let (
(key { pool-id: pool-id, depositor: tx-sender })
(position (unwrap! (map-get? positions key) ERR-NO-POSITION))
(amount (get amount position))
)
(asserts! (>= block-height (get unlock-block position)) ERR-LOCKED)

(map-delete positions key)
(try! (contract-call? .bitstake-pool-registry remove-from-total pool-id amount))
(try! (as-contract (stx-transfer? amount tx-sender tx-sender)))

(print { event: "withdrawal", pool-id: pool-id, depositor: tx-sender, amount: amount })
(ok amount)
)
)

;; ── Read-Only ─────────────────────────────────────────────────────────

(define-read-only (get-position (pool-id uint) (depositor principal))
(ok (map-get? positions { pool-id: pool-id, depositor: depositor }))
)

(define-read-only (is-locked (pool-id uint) (depositor principal))
(match (map-get? positions { pool-id: pool-id, depositor: depositor })
pos (ok (< block-height (get unlock-block pos)))
(ok false)
)
)

(define-read-only (blocks-until-unlock (pool-id uint) (depositor principal))
(match (map-get? positions { pool-id: pool-id, depositor: depositor })
pos (if (>= block-height (get unlock-block pos))
(ok u0)
(ok (- (get unlock-block pos) block-height)))
(ok u0)
)
)
Loading
Loading