Source Code

;; Clarity Version

;; Description: Implementation of core derivatives trading functionality

;; -----------------------------
;; Constants and Traits
;; -----------------------------

;; 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 (12)

FunctionAccessArgs
get-balanceread-onlyuser: principal
get-positionread-onlyposition-id: uint
get-current-priceread-only
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
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