Source Code

;; Stakied PT/YT AMM - Automated Market Maker with Time-Decay Curve
;; Enables trading of Principal Tokens (PT) against SY tokens

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u300))
(define-constant err-not-authorized (err u301))
(define-constant err-invalid-amount (err u302))
(define-constant err-insufficient-balance (err u303))
(define-constant err-insufficient-liquidity (err u304))
(define-constant err-slippage-exceeded (err u305))
(define-constant err-pool-not-initialized (err u306))
(define-constant err-pool-already-exists (err u307))
(define-constant err-zero-reserves (err u308))
(define-constant err-invalid-maturity (err u309))

;; Fee configuration (in basis points: 30 = 0.3%)
(define-constant swap-fee-bps u30)
(define-constant fee-denominator u10000)
(define-constant precision u1000000)

;; Pool state for each maturity
(define-map pools 
  uint  ;; maturity
  {
    pt-reserve: uint,
    sy-reserve: uint,
    total-lp-supply: uint,
    last-update: uint
  }
)

;; LP token balances
(define-map lp-balances {user: principal, maturity: uint} uint)

;; Read-only functions
(define-read-only (get-pool (maturity uint))
  (ok (map-get? pools maturity)))

(define-read-only (get-lp-balance (user principal) (maturity uint))
  (ok (default-to u0 (map-get? lp-balances {user: user, maturity: maturity}))))

(define-read-only (get-pool-reserves (maturity uint))
  (let ((pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized)))
    (ok {
      pt-reserve: (get pt-reserve pool-data),
      sy-reserve: (get sy-reserve pool-data)
    })
  )
)

(define-read-only (calculate-time-factor (maturity uint))
  (if (>= block-height maturity)
    precision  ;; t = 1 (maturity reached, PT = 1 SY)
    (let (
      (blocks-to-maturity (- maturity block-height))
      (total-blocks maturity)
    )
      (if (is-eq total-blocks u0)
        u0
        (/ (* (- maturity blocks-to-maturity) precision) total-blocks)
      )
    )
  )
)

(define-read-only (get-spot-price (maturity uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
    (time-factor (calculate-time-factor maturity))
  )
    (asserts! (> pt-reserve u0) err-zero-reserves)
    (asserts! (> sy-reserve u0) err-zero-reserves)
    
    ;; Simplified spot price: (SY_reserve / PT_reserve) * precision
    ;; As t->1, price->1:1
    (ok (/ (* sy-reserve precision) pt-reserve))
  )
)

(define-public (initialize-pool (maturity uint) (pt-amount uint) (sy-amount uint))
  (begin
    (asserts! (is-none (map-get? pools maturity)) err-pool-already-exists)
    (asserts! (> pt-amount u0) err-invalid-amount)
    (asserts! (> sy-amount u0) err-invalid-amount)
    ;; Transfer PT from user to AMM
    (try! (contract-call? .stakied-pt-yt-core transfer-pt pt-amount maturity tx-sender (as-contract tx-sender)))
    
    ;; Transfer SY from user to AMM
    (try! (contract-call? .stakied-sy-token transfer sy-amount tx-sender (as-contract tx-sender) none))
    
    ;; Calculate initial LP tokens (geometric mean)
    ;; LP = sqrt(PT * SY)
    (let ((initial-lp (sqrt-i (* pt-amount sy-amount))))
      (begin
        (map-set pools maturity {
          pt-reserve: pt-amount,
          sy-reserve: sy-amount,
          total-lp-supply: initial-lp,
          last-update: block-height
        })
        
        (map-set lp-balances {user: tx-sender, maturity: maturity} initial-lp)
        
        (print {
          action: "initialize-pool",
          maturity: maturity,
          pt-amount: pt-amount,
          sy-amount: sy-amount,
          lp-tokens: initial-lp
        })
        
        (ok initial-lp)
      )
    )
  )
)

;; Helper: Integer square root (Newton's method) - Unrolled for Clarity
(define-private (sqrt-i (n uint))
  (if (<= n u1)
    n
    (let (
      (x1 (/ (+ u1 n) u2))
      (x2 (/ (+ x1 (/ n x1)) u2))
      (x3 (/ (+ x2 (/ n x2)) u2))
      (x4 (/ (+ x3 (/ n x3)) u2))
      (x5 (/ (+ x4 (/ n x4)) u2))
      (x6 (/ (+ x5 (/ n x5)) u2))
      (x7 (/ (+ x6 (/ n x6)) u2))
      (x8 (/ (+ x7 (/ n x7)) u2))
    )
      (if (<= x8 x7) x8 x7)
    )
  )
)

(define-public (swap-pt-for-sy (pt-amount uint) (maturity uint) (min-sy-out uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
  )
    (asserts! (> pt-amount u0) err-invalid-amount)
    (asserts! (> pt-reserve u0) err-zero-reserves)
    (asserts! (> sy-reserve u0) err-zero-reserves)
    
    ;; Calculate swap output using constant product formula
    (let (
      (pt-after-fee (/ (* pt-amount (- fee-denominator swap-fee-bps)) fee-denominator))
      (sy-out (/ (* sy-reserve pt-after-fee) (+ pt-reserve pt-after-fee)))
    )
      (begin
        (asserts! (>= sy-out min-sy-out) err-slippage-exceeded)
        (asserts! (< sy-out sy-reserve) err-insufficient-liquidity)
        
        ;; Transfer PT from user to pool
        (try! (contract-call? .stakied-pt-yt-core transfer-pt pt-amount maturity tx-sender (as-contract tx-sender)))
        
        ;; Transfer SY from pool to user
        (let ((sender tx-sender))
          (try! (as-contract (contract-call? .stakied-sy-token transfer sy-out tx-sender sender none)))
        )
        
        ;; Update pool reserves
        (map-set pools maturity {
          pt-reserve: (+ pt-reserve pt-amount),
          sy-reserve: (- sy-reserve sy-out),
          total-lp-supply: (get total-lp-supply pool-data),
          last-update: block-height
        })
        
        (print {
          action: "swap-pt-for-sy",
          pt-in: pt-amount,
          sy-out: sy-out,
          maturity: maturity
        })
        
        (ok sy-out)
      )
    )
  )
)



(define-read-only (quote-swap-pt-for-sy (pt-amount uint) (maturity uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
  )
    (asserts! (> pt-amount u0) err-invalid-amount)
    
    (let (
      (pt-after-fee (/ (* pt-amount (- fee-denominator swap-fee-bps)) fee-denominator))
      (sy-out (/ (* sy-reserve pt-after-fee) (+ pt-reserve pt-after-fee)))
    )
      (ok {
        sy-out: sy-out,
        price-impact: (/ (* (- sy-reserve (- sy-reserve sy-out)) precision) sy-reserve),
        fee: (- pt-amount pt-after-fee)
      })
    )
  )
)

(define-read-only (quote-swap-sy-for-pt (sy-amount uint) (maturity uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
  )
    (asserts! (> sy-amount u0) err-invalid-amount)
    
    (let (
      (sy-after-fee (/ (* sy-amount (- fee-denominator swap-fee-bps)) fee-denominator))
      (pt-out (/ (* pt-reserve sy-after-fee) (+ sy-reserve sy-after-fee)))
    )
      (ok {
        pt-out: pt-out,
        price-impact: (/ (* (- pt-reserve (- pt-reserve pt-out)) precision) pt-reserve),
        fee: (- sy-amount sy-after-fee)
      })
    )
  )
)

(define-read-only (get-pool-stats (maturity uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
  )
    (ok {
      pt-reserve: (get pt-reserve pool-data),
      sy-reserve: (get sy-reserve pool-data),
      total-lp-supply: (get total-lp-supply pool-data),
      last-update: (get last-update pool-data),
      spot-price: (unwrap-panic (get-spot-price maturity)),
      time-to-maturity: (if (> maturity block-height) (- maturity block-height) u0)
    })
  )
)

(define-public (swap-sy-for-pt (sy-amount uint) (maturity uint) (min-pt-out uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
  )
    (asserts! (> sy-amount u0) err-invalid-amount)
    (asserts! (> pt-reserve u0) err-zero-reserves)
    (asserts! (> sy-reserve u0) err-zero-reserves)
    
    ;; Calculate swap output
    (let (
      (sy-after-fee (/ (* sy-amount (- fee-denominator swap-fee-bps)) fee-denominator))
      (pt-out (/ (* pt-reserve sy-after-fee) (+ sy-reserve sy-after-fee)))
    )
      (begin
        (asserts! (>= pt-out min-pt-out) err-slippage-exceeded)
        (asserts! (< pt-out pt-reserve) err-insufficient-liquidity)
        
        ;; Transfer SY from user to pool
        (try! (contract-call? .stakied-sy-token transfer sy-amount tx-sender (as-contract tx-sender) none))
        
        ;; Transfer PT from pool to user
        (let ((sender tx-sender))
          (try! (as-contract (contract-call? .stakied-pt-yt-core transfer-pt pt-out maturity tx-sender sender)))
        )
        
        ;; Update pool reserves
        (map-set pools maturity {
          pt-reserve: (- pt-reserve pt-out),
          sy-reserve: (+ sy-reserve sy-amount),
          total-lp-supply: (get total-lp-supply pool-data),
          last-update: block-height
        })
        
        (print {
          action: "swap-sy-for-pt",
          sy-in: sy-amount,
          pt-out: pt-out,
          maturity: maturity
        })
        
        (ok pt-out)
      )
    )
  )
)

(define-public (add-liquidity (maturity uint) (pt-amount uint) (sy-amount uint) (min-lp-out uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
    (total-lp (get total-lp-supply pool-data))
  )
    (asserts! (> pt-amount u0) err-invalid-amount)
    (asserts! (> sy-amount u0) err-invalid-amount)
    
    ;; Calculate LP tokens to mint
    (let (
      (lp-from-pt (/ (* pt-amount total-lp) pt-reserve))
      (lp-from-sy (/ (* sy-amount total-lp) sy-reserve))
      (lp-out (if (< lp-from-pt lp-from-sy) lp-from-pt lp-from-sy))
    )
      (asserts! (>= lp-out min-lp-out) err-slippage-exceeded)
      
      ;; Calculate actual amounts to deposit
      (let (
        (actual-pt (/ (* lp-out pt-reserve) total-lp))
        (actual-sy (/ (* lp-out sy-reserve) total-lp))
      )
        (begin
          ;; Transfer PT from user to pool
          (try! (contract-call? .stakied-pt-yt-core transfer-pt actual-pt maturity tx-sender (as-contract tx-sender)))
          
          ;; Transfer SY from user to pool
          (try! (contract-call? .stakied-sy-token transfer actual-sy tx-sender (as-contract tx-sender) none))
          
          ;; Update pool state
          (map-set pools maturity {
            pt-reserve: (+ pt-reserve actual-pt),
            sy-reserve: (+ sy-reserve actual-sy),
            total-lp-supply: (+ total-lp lp-out),
            last-update: block-height
          })
          
          ;; Mint LP tokens to user
          (let ((current-lp (default-to u0 (map-get? lp-balances {user: tx-sender, maturity: maturity}))))
            (map-set lp-balances {user: tx-sender, maturity: maturity} (+ current-lp lp-out))
          )
          
          (print {
            action: "add-liquidity",
            pt-added: actual-pt,
            sy-added: actual-sy,
            lp-minted: lp-out,
            maturity: maturity
          })
          
          (ok {pt: actual-pt, sy: actual-sy, lp: lp-out})
        )
      )
    )
  )
)

(define-public (remove-liquidity (maturity uint) (lp-amount uint) (min-pt-out uint) (min-sy-out uint))
  (let (
    (pool-data (unwrap! (map-get? pools maturity) err-pool-not-initialized))
    (pt-reserve (get pt-reserve pool-data))
    (sy-reserve (get sy-reserve pool-data))
    (total-lp (get total-lp-supply pool-data))
    (user-lp (default-to u0 (map-get? lp-balances {user: tx-sender, maturity: maturity})))
  )
    (asserts! (> lp-amount u0) err-invalid-amount)
    (asserts! (>= user-lp lp-amount) err-insufficient-balance)
    
    ;; Calculate amounts to return
    (let (
      (pt-out (/ (* lp-amount pt-reserve) total-lp))
      (sy-out (/ (* lp-amount sy-reserve) total-lp))
    )
      (begin
        (asserts! (>= pt-out min-pt-out) err-slippage-exceeded)
        (asserts! (>= sy-out min-sy-out) err-slippage-exceeded)
        
        ;; Burn LP tokens
        (map-set lp-balances {user: tx-sender, maturity: maturity} (- user-lp lp-amount))
        
        ;; Update pool reserves
        (map-set pools maturity {
          pt-reserve: (- pt-reserve pt-out),
          sy-reserve: (- sy-reserve sy-out),
          total-lp-supply: (- total-lp lp-amount),
          last-update: block-height
        })
        
        ;; Transfer PT to user
        (let ((sender tx-sender))
          (begin
            (try! (as-contract (contract-call? .stakied-pt-yt-core transfer-pt pt-out maturity tx-sender sender)))
            
            ;; Transfer SY to user
            (try! (as-contract (contract-call? .stakied-sy-token transfer sy-out tx-sender sender none)))
          )
        )
        
        (print {
          action: "remove-liquidity",
          lp-burned: lp-amount,
          pt-returned: pt-out,
          sy-returned: sy-out,
          maturity: maturity
        })
        
        (ok {pt: pt-out, sy: sy-out})
      )
    )
  )
)

Functions (14)

FunctionAccessArgs
get-poolread-onlymaturity: uint
get-lp-balanceread-onlyuser: principal, maturity: uint
get-pool-reservesread-onlymaturity: uint
calculate-time-factorread-onlymaturity: uint
get-spot-priceread-onlymaturity: uint
initialize-poolpublicmaturity: uint, pt-amount: uint, sy-amount: uint
sqrt-iprivaten: uint
swap-pt-for-sypublicpt-amount: uint, maturity: uint, min-sy-out: uint
quote-swap-pt-for-syread-onlypt-amount: uint, maturity: uint
quote-swap-sy-for-ptread-onlysy-amount: uint, maturity: uint
get-pool-statsread-onlymaturity: uint
swap-sy-for-ptpublicsy-amount: uint, maturity: uint, min-pt-out: uint
add-liquiditypublicmaturity: uint, pt-amount: uint, sy-amount: uint, min-lp-out: uint
remove-liquiditypublicmaturity: uint, lp-amount: uint, min-pt-out: uint, min-sy-out: uint