Source Code

;; title: strategy-alex-pool
;; version: 1.0.0
;; summary: ALEX DEX liquidity pool strategy
;; description: Automated liquidity provision on ALEX - Clarity 4

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-UNAUTHORIZED (err u2000))
(define-constant ERR-INVALID-AMOUNT (err u2001))
(define-constant ERR-POOL-NOT-FOUND (err u2002))
(define-constant ERR-INSUFFICIENT-LIQUIDITY (err u2003))
(define-constant ERR-SLIPPAGE-EXCEEDED (err u2004))
(define-constant ERR-STRATEGY-PAUSED (err u2005))

;; Strategy parameters
(define-constant MAX-SLIPPAGE u500)  ;; 5% in basis points
(define-constant REBALANCE-THRESHOLD u1000)  ;; 10%

;; Data Variables
(define-data-var strategy-paused bool false)
(define-data-var total-liquidity-provided uint u0)
(define-data-var total-fees-earned uint u0)
(define-data-var next-position-id uint u1)

;; Data Maps - Using stacks-block-time for Clarity 4
(define-map liquidity-positions uint {
  provider: principal,
  pool-token-a: principal,
  pool-token-b: principal,
  amount-a: uint,
  amount-b: uint,
  lp-tokens: uint,
  entry-price: uint,
  created-at: uint,     ;; Clarity 4: Unix timestamp
  last-rebalance: uint, ;; Clarity 4: Unix timestamp
  fees-earned: uint,
  is-active: bool
})

(define-map pool-stats principal {
  pool-id: principal,
  total-volume: uint,
  total-fees: uint,
  apy: uint,
  last-update: uint  ;; Clarity 4: Unix timestamp
})

(define-map user-positions principal (list 50 uint))

(define-map rebalance-history uint {
  position-id: uint,
  old-ratio: uint,
  new-ratio: uint,
  rebalanced-at: uint  ;; Clarity 4: Unix timestamp
})

(define-data-var next-rebalance-id uint u1)

;; Private Functions

(define-private (calculate-lp-tokens (amount-a uint) (amount-b uint))
  ;; Simplified LP token calculation
  (/ (+ amount-a amount-b) u2)
)

(define-private (check-slippage (expected uint) (actual uint))
  (let (
    (difference (if (> expected actual)
                   (- expected actual)
                   (- actual expected)))
    (slippage-percent (/ (* difference u10000) expected))
  )
    (<= slippage-percent MAX-SLIPPAGE)
  )
)

;; Public Functions

;; Add liquidity to ALEX pool
(define-public (add-liquidity
  (pool-token-a principal)
  (pool-token-b principal)
  (amount-a uint)
  (amount-b uint)
  (min-lp-tokens uint))
  (let (
    (position-id (var-get next-position-id))
    (lp-tokens (calculate-lp-tokens amount-a amount-b))
    (entry-price (/ (* amount-a u10000) amount-b))
  )
    (asserts! (not (var-get strategy-paused)) ERR-STRATEGY-PAUSED)
    (asserts! (> amount-a u0) ERR-INVALID-AMOUNT)
    (asserts! (> amount-b u0) ERR-INVALID-AMOUNT)
    (asserts! (>= lp-tokens min-lp-tokens) ERR-SLIPPAGE-EXCEEDED)

    ;; Transfer tokens to contract (simplified - would need actual token transfers)
    ;; (try! (contract-call? pool-token-a transfer amount-a tx-sender tx-sender none))
    ;; (try! (contract-call? pool-token-b transfer amount-b tx-sender tx-sender none))

    ;; Record position
    (map-set liquidity-positions position-id {
      provider: tx-sender,
      pool-token-a: pool-token-a,
      pool-token-b: pool-token-b,
      amount-a: amount-a,
      amount-b: amount-b,
      lp-tokens: lp-tokens,
      entry-price: entry-price,
      created-at: stacks-block-time,
      last-rebalance: stacks-block-time,
      fees-earned: u0,
      is-active: true
    })

    (var-set next-position-id (+ position-id u1))
    (var-set total-liquidity-provided (+ (var-get total-liquidity-provided) (+ amount-a amount-b)))

    (print {
      event: "liquidity-added",
      position-id: position-id,
      provider: tx-sender,
      amount-a: amount-a,
      amount-b: amount-b,
      lp-tokens: lp-tokens,
      timestamp: stacks-block-time
    })

    (ok position-id)
  )
)

;; Remove liquidity from ALEX pool
(define-public (remove-liquidity (position-id uint) (lp-tokens-to-burn uint))
  (let (
    (position (unwrap! (map-get? liquidity-positions position-id) ERR-POOL-NOT-FOUND))
    (total-lp-tokens (get lp-tokens position))
    (amount-a-to-return (/ (* (get amount-a position) lp-tokens-to-burn) total-lp-tokens))
    (amount-b-to-return (/ (* (get amount-b position) lp-tokens-to-burn) total-lp-tokens))
    (remaining-lp (- total-lp-tokens lp-tokens-to-burn))
  )
    (asserts! (is-eq tx-sender (get provider position)) ERR-UNAUTHORIZED)
    (asserts! (get is-active position) ERR-POOL-NOT-FOUND)
    (asserts! (<= lp-tokens-to-burn total-lp-tokens) ERR-INSUFFICIENT-LIQUIDITY)

    ;; Transfer tokens back (simplified)
    ;; (try! (begin (contract-call? pool-token-a transfer amount-a-to-return tx-sender (get provider position) none)))
    ;; (try! (begin (contract-call? pool-token-b transfer amount-b-to-return tx-sender (get provider position) none)))

    ;; Update or delete position
    (if (is-eq remaining-lp u0)
      (map-set liquidity-positions position-id
        (merge position { is-active: false }))
      (map-set liquidity-positions position-id
        (merge position {
          lp-tokens: remaining-lp,
          amount-a: (- (get amount-a position) amount-a-to-return),
          amount-b: (- (get amount-b position) amount-b-to-return)
        }))
    )

    (print {
      event: "liquidity-removed",
      position-id: position-id,
      provider: tx-sender,
      amount-a: amount-a-to-return,
      amount-b: amount-b-to-return,
      timestamp: stacks-block-time
    })

    (ok { amount-a: amount-a-to-return, amount-b: amount-b-to-return })
  )
)

;; Rebalance position
(define-public (rebalance-position (position-id uint))
  (let (
    (position (unwrap! (map-get? liquidity-positions position-id) ERR-POOL-NOT-FOUND))
    (rebalance-id (var-get next-rebalance-id))
    (current-ratio (/ (* (get amount-a position) u10000) (get amount-b position)))
    (entry-ratio (get entry-price position))
  )
    (asserts! (is-eq tx-sender (get provider position)) ERR-UNAUTHORIZED)
    (asserts! (get is-active position) ERR-POOL-NOT-FOUND)

    ;; Record rebalance
    (map-set rebalance-history rebalance-id {
      position-id: position-id,
      old-ratio: entry-ratio,
      new-ratio: current-ratio,
      rebalanced-at: stacks-block-time
    })

    ;; Update position
    (map-set liquidity-positions position-id
      (merge position {
        last-rebalance: stacks-block-time,
        entry-price: current-ratio
      }))

    (var-set next-rebalance-id (+ rebalance-id u1))

    (print {
      event: "position-rebalanced",
      position-id: position-id,
      old-ratio: entry-ratio,
      new-ratio: current-ratio,
      timestamp: stacks-block-time
    })

    (ok true)
  )
)

;; Claim fees
(define-public (claim-fees (position-id uint))
  (let (
    (position (unwrap! (map-get? liquidity-positions position-id) ERR-POOL-NOT-FOUND))
    (fees (get fees-earned position))
  )
    (asserts! (is-eq tx-sender (get provider position)) ERR-UNAUTHORIZED)
    (asserts! (> fees u0) ERR-INVALID-AMOUNT)

    ;; Transfer fees (simplified)
    ;; (try! (begin (stx-transfer? fees tx-sender (get provider position))))

    ;; Update position
    (map-set liquidity-positions position-id
      (merge position { fees-earned: u0 }))

    (var-set total-fees-earned (+ (var-get total-fees-earned) fees))

    (print {
      event: "fees-claimed",
      position-id: position-id,
      provider: tx-sender,
      amount: fees,
      timestamp: stacks-block-time
    })

    (ok fees)
  )
)

;; Pause strategy
(define-public (pause-strategy)
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-UNAUTHORIZED)
    (var-set strategy-paused true)
    (ok true)
  )
)

;; Resume strategy
(define-public (resume-strategy)
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-UNAUTHORIZED)
    (var-set strategy-paused false)
    (ok true)
  )
)

;; Read-Only Functions

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

(define-read-only (get-pool-stats (pool-id principal))
  (map-get? pool-stats pool-id)
)

(define-read-only (get-total-liquidity)
  (var-get total-liquidity-provided)
)

(define-read-only (get-total-fees-earned)
  (var-get total-fees-earned)
)

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

(define-read-only (get-rebalance-record (rebalance-id uint))
  (map-get? rebalance-history rebalance-id)
)

Functions (14)

FunctionAccessArgs
calculate-lp-tokensprivateamount-a: uint, amount-b: uint
check-slippageprivateexpected: uint, actual: uint
add-liquiditypublicpool-token-a: principal, pool-token-b: principal, amount-a: uint, amount-b: uint, min-lp-tokens: uint
remove-liquiditypublicposition-id: uint, lp-tokens-to-burn: uint
rebalance-positionpublicposition-id: uint
claim-feespublicposition-id: uint
pause-strategypublic
resume-strategypublic
get-positionread-onlyposition-id: uint
get-pool-statsread-onlypool-id: principal
get-total-liquidityread-only
get-total-fees-earnedread-only
is-strategy-pausedread-only
get-rebalance-recordread-onlyrebalance-id: uint