perp-dex-for-prediction-market

SP1V95DB4JK47QVPJBXCEN6MT35JK84CQ4CWS15DQ

Source Code

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-found (err u101))
(define-constant err-unauthorized (err u102))
(define-constant err-already-exists (err u103))
(define-constant err-invalid-amount (err u104))
(define-constant err-insufficient-margin (err u105))
(define-constant err-position-liquidated (err u106))

(define-data-var market-nonce uint u0)
(define-data-var position-nonce uint u0)

(define-map perp-markets
  uint
  {
    market-name: (string-ascii 50),
    underlying-asset: (string-ascii 30),
    oracle-source: (buff 32),
    mark-price: uint,
    index-price: uint,
    funding-rate: int,
    total-long: uint,
    total-short: uint,
    active: bool
  }
)

(define-map perpetual-positions
  uint
  {
    trader: principal,
    market-id: uint,
    position-size: int,
    entry-price: uint,
    margin: uint,
    leverage: uint,
    liquidation-price: uint,
    unrealized-pnl: int,
    open: bool
  }
)

(define-map trader-positions principal (list 100 uint))
(define-map market-positions uint (list 500 uint))

(define-public (create-perp-market (market-name (string-ascii 50)) (underlying-asset (string-ascii 30)) (oracle-source (buff 32)) (initial-price uint))
  (let
    (
      (market-id (+ (var-get market-nonce) u1))
    )
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> initial-price u0) err-invalid-amount)
    (map-set perp-markets market-id
      {
        market-name: market-name,
        underlying-asset: underlying-asset,
        oracle-source: oracle-source,
        mark-price: initial-price,
        index-price: initial-price,
        funding-rate: 0,
        total-long: u0,
        total-short: u0,
        active: true
      }
    )
    (var-set market-nonce market-id)
    (ok market-id)
  )
)

(define-public (open-long-position (market-id uint) (size uint) (margin uint) (leverage uint))
  (let
    (
      (market (unwrap! (map-get? perp-markets market-id) err-not-found))
      (position-id (+ (var-get position-nonce) u1))
      (position-value (* size (get mark-price market)))
      (required-margin (/ position-value leverage))
    )
    (asserts! (get active market) err-not-found)
    (asserts! (>= margin required-margin) err-insufficient-margin)
    (asserts! (> size u0) err-invalid-amount)
    (asserts! (and (>= leverage u1) (<= leverage u50)) err-invalid-amount)
    (try! (stx-transfer? margin tx-sender (as-contract tx-sender)))
    (let
      (
        (liquidation-price (/ (* (get mark-price market) (- leverage u1)) leverage))
      )
      (map-set perpetual-positions position-id
        {
          trader: tx-sender,
          market-id: market-id,
          position-size: (to-int size),
          entry-price: (get mark-price market),
          margin: margin,
          leverage: leverage,
          liquidation-price: liquidation-price,
          unrealized-pnl: 0,
          open: true
        }
      )
      (map-set perp-markets market-id (merge market {
        total-long: (+ (get total-long market) size)
      }))
      (map-set trader-positions tx-sender
        (unwrap-panic (as-max-len? (append (default-to (list) (map-get? trader-positions tx-sender)) position-id) u100)))
      (map-set market-positions market-id
        (unwrap-panic (as-max-len? (append (default-to (list) (map-get? market-positions market-id)) position-id) u500)))
      (var-set position-nonce position-id)
      (ok position-id)
    )
  )
)

(define-public (open-short-position (market-id uint) (size uint) (margin uint) (leverage uint))
  (let
    (
      (market (unwrap! (map-get? perp-markets market-id) err-not-found))
      (position-id (+ (var-get position-nonce) u1))
      (position-value (* size (get mark-price market)))
      (required-margin (/ position-value leverage))
    )
    (asserts! (get active market) err-not-found)
    (asserts! (>= margin required-margin) err-insufficient-margin)
    (asserts! (> size u0) err-invalid-amount)
    (asserts! (and (>= leverage u1) (<= leverage u50)) err-invalid-amount)
    (try! (stx-transfer? margin tx-sender (as-contract tx-sender)))
    (let
      (
        (liquidation-price (/ (* (get mark-price market) (+ leverage u1)) leverage))
      )
      (map-set perpetual-positions position-id
        {
          trader: tx-sender,
          market-id: market-id,
          position-size: (to-int (* size u1000000)),
          entry-price: (get mark-price market),
          margin: margin,
          leverage: leverage,
          liquidation-price: liquidation-price,
          unrealized-pnl: 0,
          open: true
        }
      )
      (map-set perp-markets market-id (merge market {
        total-short: (+ (get total-short market) size)
      }))
      (map-set trader-positions tx-sender
        (unwrap-panic (as-max-len? (append (default-to (list) (map-get? trader-positions tx-sender)) position-id) u100)))
      (map-set market-positions market-id
        (unwrap-panic (as-max-len? (append (default-to (list) (map-get? market-positions market-id)) position-id) u500)))
      (var-set position-nonce position-id)
      (ok position-id)
    )
  )
)

(define-public (close-position (position-id uint))
  (let
    (
      (position (unwrap! (map-get? perpetual-positions position-id) err-not-found))
      (market (unwrap! (map-get? perp-markets (get market-id position)) err-not-found))
    )
    (asserts! (is-eq tx-sender (get trader position)) err-unauthorized)
    (asserts! (get open position) err-already-exists)
    (let
      (
        (is-long (> (get position-size position) 0))
        (size (if is-long (to-uint (get position-size position)) (to-uint (- 0 (get position-size position)))))
        (pnl-value (if is-long
          (to-int (- (* size (get mark-price market)) (* size (get entry-price position))))
          (to-int (- (* size (get entry-price position)) (* size (get mark-price market))))))
        (final-balance (to-uint (+ (to-int (get margin position)) pnl-value)))
      )
      (if (> final-balance u0)
        (try! (as-contract (stx-transfer? final-balance tx-sender (get trader position))))
        true
      )
      (map-set perpetual-positions position-id (merge position {
        open: false,
        unrealized-pnl: pnl-value
      }))
      (map-set perp-markets (get market-id position) (merge market {
        total-long: (if is-long (- (get total-long market) size) (get total-long market)),
        total-short: (if is-long (get total-short market) (- (get total-short market) size))
      }))
      (ok final-balance)
    )
  )
)

(define-public (update-mark-price (market-id uint) (new-price uint))
  (let
    (
      (market (unwrap! (map-get? perp-markets market-id) err-not-found))
    )
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (map-set perp-markets market-id (merge market {mark-price: new-price}))
    (ok true)
  )
)

(define-read-only (get-perp-market (market-id uint))
  (ok (map-get? perp-markets market-id))
)

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

(define-read-only (get-trader-positions (trader principal))
  (ok (map-get? trader-positions trader))
)

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

Functions (9)

FunctionAccessArgs
create-perp-marketpublicmarket-name: (string-ascii 50
open-long-positionpublicmarket-id: uint, size: uint, margin: uint, leverage: uint
open-short-positionpublicmarket-id: uint, size: uint, margin: uint, leverage: uint
close-positionpublicposition-id: uint
update-mark-pricepublicmarket-id: uint, new-price: uint
get-perp-marketread-onlymarket-id: uint
get-positionread-onlyposition-id: uint
get-trader-positionsread-onlytrader: principal
get-market-positionsread-onlymarket-id: uint