Source Code

;; treasury.clar
;; City Treasury - Bitzion model
;;
;; - Accepts sBTC deposits, mints governance tokens
;; - Coordinator has DIRECT spending authority (no voting per spend)
;; - Board can dilute (mint tokens) as "tax"
;; - Hodlers can deposit/withdraw freely

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-paused (err u101))
(define-constant err-insufficient-balance (err u102))
(define-constant err-invalid-amount (err u103))
(define-constant err-transfer-failed (err u104))
(define-constant err-not-coordinator (err u105))
(define-constant err-not-board (err u106))
(define-constant err-no-coordinator (err u107))

;; Conversion rate: 1 sBTC (8 decimals) = 1,000,000 tokens (6 decimals)
(define-constant sbtc-to-token-multiplier u10000)

;; State
(define-data-var paused bool false)
(define-data-var total-deposits uint u0)

;; Track individual contributions
(define-map contributions principal uint)

;; Coordinator - has direct spending authority
(define-data-var coordinator (optional principal) none)

;; Board contract - can dilute
(define-data-var board-contract principal contract-owner)

;; sBTC contract reference (set via set-sbtc-contract after deployment)
(define-data-var sbtc-contract principal contract-owner)

;; Deposit sBTC and receive governance tokens
(define-public (deposit (amount uint))
  (let
    (
      (depositor tx-sender)
      (token-amount (* amount sbtc-to-token-multiplier))
      (current-contribution (default-to u0 (map-get? contributions depositor)))
    )
    (asserts! (not (var-get paused)) err-paused)
    (asserts! (> amount u0) err-invalid-amount)

    ;; Transfer sBTC from depositor to treasury
    (try! (contract-call? .mock-sbtc transfer amount depositor (as-contract tx-sender) none))

    ;; Mint governance tokens to depositor
    (try! (contract-call? .city-btc-token mint token-amount depositor))

    ;; Update contribution tracking
    (map-set contributions depositor (+ current-contribution amount))
    (var-set total-deposits (+ (var-get total-deposits) amount))

    (print {event: "deposit", depositor: depositor, sbtc-amount: amount, tokens-minted: token-amount})
    (ok token-amount)))

;; Withdraw sBTC by burning governance tokens
(define-public (withdraw (token-amount uint))
  (let
    (
      (withdrawer tx-sender)
      (sbtc-amount (/ token-amount sbtc-to-token-multiplier))
      (current-contribution (default-to u0 (map-get? contributions withdrawer)))
    )
    (asserts! (not (var-get paused)) err-paused)
    (asserts! (> token-amount u0) err-invalid-amount)
    (asserts! (>= current-contribution sbtc-amount) err-insufficient-balance)

    ;; Burn governance tokens
    (try! (contract-call? .city-btc-token burn token-amount withdrawer))

    ;; Transfer sBTC back to withdrawer
    (try! (as-contract (contract-call? .mock-sbtc transfer sbtc-amount tx-sender withdrawer none)))

    ;; Update contribution tracking
    (map-set contributions withdrawer (- current-contribution sbtc-amount))
    (var-set total-deposits (- (var-get total-deposits) sbtc-amount))

    (print {event: "withdraw", withdrawer: withdrawer, tokens-burned: token-amount, sbtc-returned: sbtc-amount})
    (ok sbtc-amount)))

;; Coordinator direct spend - NO voting required
;; The coordinator has full authority over treasury spending
(define-public (coordinator-spend (amount uint) (recipient principal) (memo (optional (buff 34))))
  (let
    (
      (current-coordinator (unwrap! (var-get coordinator) err-no-coordinator))
    )
    (asserts! (is-eq tx-sender current-coordinator) err-not-coordinator)
    (asserts! (not (var-get paused)) err-paused)
    (asserts! (> amount u0) err-invalid-amount)

    ;; Transfer sBTC from treasury to recipient
    (try! (as-contract (contract-call? .mock-sbtc transfer amount tx-sender recipient memo)))

    (print {event: "coordinator-spend", coordinator: current-coordinator, amount: amount, recipient: recipient})
    (ok true)))

;; Board dilution - mint new tokens (tax)
;; Only callable by board contract after approval
(define-public (board-dilute (amount uint))
  (begin
    (asserts! (is-eq contract-caller (var-get board-contract)) err-not-board)
    (asserts! (> amount u0) err-invalid-amount)

    ;; Mint tokens to treasury itself (for coordinator to spend)
    (try! (contract-call? .city-btc-token mint amount (as-contract tx-sender)))

    (print {event: "board-dilution", amount: amount})
    (ok true)))

;; Admin Functions

;; Set coordinator - only callable by board contract
(define-public (set-coordinator (new-coordinator principal))
  (begin
    (asserts! (is-eq contract-caller (var-get board-contract)) err-not-board)
    (var-set coordinator (some new-coordinator))
    (print {event: "coordinator-set", coordinator: new-coordinator})
    (ok true)))

;; Set board contract (one-time setup)
(define-public (set-board-contract (board principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set board-contract board)
    (ok true)))

;; Emergency pause
(define-public (set-paused (is-paused bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set paused is-paused)
    (print {event: "pause-toggled", paused: is-paused})
    (ok true)))

;; Set sBTC contract reference
(define-public (set-sbtc-contract (sbtc principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set sbtc-contract sbtc)
    (ok true)))

;; Read-only functions

(define-read-only (get-contribution (account principal))
  (default-to u0 (map-get? contributions account)))

(define-read-only (get-total-deposits)
  (var-get total-deposits))

(define-read-only (get-treasury-balance)
  (contract-call? .mock-sbtc get-balance (as-contract tx-sender)))

(define-read-only (get-paused)
  (var-get paused))

(define-read-only (get-coordinator)
  (var-get coordinator))

(define-read-only (get-board-contract)
  (var-get board-contract))

(define-read-only (calculate-tokens-for-deposit (sbtc-amount uint))
  (* sbtc-amount sbtc-to-token-multiplier))

(define-read-only (calculate-sbtc-for-withdrawal (token-amount uint))
  (/ token-amount sbtc-to-token-multiplier))

Functions (16)

FunctionAccessArgs
depositpublicamount: uint
withdrawpublictoken-amount: uint
coordinator-spendpublicamount: uint, recipient: principal, memo: (optional (buff 34
board-dilutepublicamount: uint
set-coordinatorpublicnew-coordinator: principal
set-board-contractpublicboard: principal
set-pausedpublicis-paused: bool
set-sbtc-contractpublicsbtc: principal
get-contributionread-onlyaccount: principal
get-total-depositsread-only
get-treasury-balanceread-only
get-pausedread-only
get-coordinatorread-only
get-board-contractread-only
calculate-tokens-for-depositread-onlysbtc-amount: uint
calculate-sbtc-for-withdrawalread-onlytoken-amount: uint