Source Code

;; title: sbtc-fundr
;; version:
;; summary:
;; description:

;; Define error codes
(define-constant ERR-UNAUTHORIZED (err u100))
(define-constant ERR-INVALID-AMOUNT (err u101))
(define-constant ERR-INSUFFICIENT-BALANCE (err u102))
(define-constant ERR-INVALID-POSITION (err u103))
(define-constant ERR-INSUFFICIENT-COLLATERAL (err u104))

;; Minimum collateral ratio (150%)
(define-constant MIN-COLLATERAL-RATIO u150)

;; Position types
(define-constant TYPE-LONG u1)
(define-constant TYPE-SHORT u2)

;; -----------------------------
;; Data Maps and Variables
;; -----------------------------

;; Track user balances
(define-map balances 
    principal 
    { stx-balance: uint })

;; Track positions
(define-map positions
    uint
    { owner: principal,
      position-type: uint,
      size: uint,
      entry-price: uint,
      leverage: uint,
      collateral: uint,
      liquidation-price: uint,
      opened-at: uint })

;; Position counter
(define-data-var position-counter uint u0)

;; Contract admin
(define-data-var contract-owner principal tx-sender)

;; Price oracle (simplified for testnet)
(define-data-var current-price uint u0)

;; -----------------------------
;; Read-Only Functions
;; -----------------------------

(define-read-only (get-balance (user principal))
    (default-to 
        { stx-balance: u0 }
        (map-get? balances user)))

(define-read-only (get-position (position-id uint))
    (map-get? positions position-id))

(define-read-only (get-current-price)
    (ok (var-get current-price)))

;; Calculate liquidation price
(define-read-only (calculate-liquidation-price
    (entry-price uint)
    (position-type uint)
    (leverage uint))
    (if (is-eq position-type TYPE-LONG)
        ;; Long position liquidation price
        (ok (/ (* entry-price (- u100 (/ u100 leverage))) u100))
        ;; Short position liquidation price
        (ok (/ (* entry-price (+ u100 (/ u100 leverage))) u100))))

;; Get position type as ASCII string (Clarity 4)
(define-read-only (get-position-type-string (position-type uint))
    (if (is-eq position-type TYPE-LONG)
        (ok "LONG")
        (if (is-eq position-type TYPE-SHORT)
            (ok "SHORT")
            (err "UNKNOWN"))))

;; Get position status including time held (Clarity 4)
(define-read-only (get-position-status (position-id uint))
    (match (get-position position-id)
        position
            (let ((time-held (- stacks-block-time (get opened-at position))))
                (ok {
                    owner-string: (unwrap-panic (to-ascii? (get owner position))),
                    position-type-string: (unwrap-panic (get-position-type-string (get position-type position))),
                    time-held: time-held
                }))
        (err "Position not found")))

;; Check if position is at risk of liquidation (Clarity 4)
(define-read-only (is-position-at-risk (position-id uint))
    (match (get-position position-id)
        position
            (let ((current-price-val (var-get current-price))
                  (at-risk (if (is-eq (get position-type position) TYPE-LONG)
                              (<= current-price-val (/ (* (get liquidation-price position) u105) u100))
                              (>= current-price-val (/ (* (get liquidation-price position) u95) u100)))))
                (ok {
                    at-risk: at-risk,
                    at-risk-string: (unwrap-panic (to-ascii? at-risk))
                }))
        (err "Position not found")))

;; -----------------------------
;; Public Functions
;; -----------------------------

;; Deposit collateral
(define-public (deposit-collateral (amount uint))
    (let ((current-balance (get stx-balance (get-balance tx-sender))))
        (ok (map-set balances
            tx-sender
            { stx-balance: (+ current-balance amount) }))))

;; Withdraw collateral
(define-public (withdraw-collateral (amount uint))
    (let ((current-balance (get stx-balance (get-balance tx-sender))))
        (asserts! (>= current-balance amount) ERR-INSUFFICIENT-BALANCE)
        (ok (map-set balances
            tx-sender
            { stx-balance: (- current-balance amount) }))))

;; Open position
(define-public (open-position 
    (position-type uint)
    (size uint)
    (leverage uint))
    (let 
        ((required-collateral (/ (* size (var-get current-price)) leverage))
         (current-balance (get stx-balance (get-balance tx-sender)))
         (position-id (+ (var-get position-counter) u1))
         (entry-price (var-get current-price)))
        
        ;; Verify conditions
        (asserts! (or (is-eq position-type TYPE-LONG) 
                     (is-eq position-type TYPE-SHORT)) ERR-INVALID-POSITION)
        (asserts! (>= current-balance required-collateral) ERR-INSUFFICIENT-COLLATERAL)
        
        ;; Calculate liquidation price
        (let ((liquidation-price (unwrap! (calculate-liquidation-price 
                                         entry-price 
                                         position-type 
                                         leverage) ERR-INVALID-POSITION)))
            
            ;; Create position
            (map-set positions position-id
                { owner: tx-sender,
                  position-type: position-type,
                  size: size,
                  entry-price: entry-price,
                  leverage: leverage,
                  collateral: required-collateral,
                  liquidation-price: liquidation-price,
                  opened-at: stacks-block-time })
            
            ;; Update balance
            (map-set balances 
                tx-sender 
                { stx-balance: (- current-balance required-collateral) })
            
            ;; Increment position counter
            (var-set position-counter position-id)
            (ok position-id))))

;; Close position
(define-public (close-position (position-id uint))
    (let ((position (unwrap! (get-position position-id) ERR-INVALID-POSITION)))
        ;; Verify owner
        (asserts! (is-eq (get owner position) tx-sender) ERR-UNAUTHORIZED)

        ;; Calculate PnL
        (let ((pnl (calculate-pnl position))
              (current-balance (get stx-balance (get-balance tx-sender))))
            ;; Return collateral + PnL to user balance
            (map-set balances
                tx-sender
                { stx-balance: (+ current-balance (+ (get collateral position) pnl)) })

            ;; Delete position
            (map-delete positions position-id)
            (ok true))))

;; -----------------------------
;; Private Functions
;; -----------------------------

;; Calculate PnL (simplified)
(define-private (calculate-pnl (position {owner: principal,
                                        position-type: uint,
                                        size: uint,
                                        entry-price: uint,
                                        leverage: uint,
                                        collateral: uint,
                                        liquidation-price: uint,
                                        opened-at: uint}))
    (let ((current-price-local (var-get current-price))
          (price-diff (if (is-eq (get position-type position) TYPE-LONG)
                         (- current-price-local (get entry-price position))
                         (- (get entry-price position) current-price-local))))
        (* price-diff (get size position))))

;; -----------------------------
;; Admin Functions
;; -----------------------------

;; Update price (would be replaced by oracle in production)
(define-public (update-price (new-price uint))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-UNAUTHORIZED)
        (var-set current-price new-price)
        (ok true)))

;; Update contract owner
(define-public (set-contract-owner (new-owner principal))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-UNAUTHORIZED)
        (var-set contract-owner new-owner)
        (ok true)))

Functions (14)

FunctionAccessArgs
get-balanceread-onlyuser: principal
get-positionread-onlyposition-id: uint
get-current-priceread-only
calculate-liquidation-priceread-onlyentry-price: uint, position-type: uint, leverage: uint
get-position-type-stringread-onlyposition-type: uint
get-position-statusread-onlyposition-id: uint
is-position-at-riskread-onlyposition-id: uint
deposit-collateralpublicamount: uint
withdraw-collateralpublicamount: uint
open-positionpublicposition-type: uint, size: uint, leverage: uint
close-positionpublicposition-id: uint
calculate-pnlprivateposition: {owner: principal, position-type: uint, size: uint, entry-price: uint, leverage: uint, collateral: uint, liquidation-price: uint, opened-at: uint}
update-pricepublicnew-price: uint
set-contract-ownerpublicnew-owner: principal