Source Code

;; halo-vault-v3.clar
;; Multi-asset yield vault with dynamic asset registry
;;
;; Improvements over v2:
;; - Dynamic asset registry (not limited to 3 assets)
;; - USDCx (primary stablecoin), sBTC, STX, hUSD support
;; - Strategy metadata per asset
;; - Generalized deposit/withdraw functions (not per-asset)
;; - Synthetix-style yield accumulator per asset (proven pattern)
;;
;; Dependencies: halo-sip010-trait

(use-trait ft-trait .halo-sip010-trait.sip-010-trait)

;; ============================================
;; CONSTANTS
;; ============================================

(define-constant CONTRACT_OWNER tx-sender)

(define-constant ERR_NOT_AUTHORIZED (err u800))
(define-constant ERR_INVALID_AMOUNT (err u801))
(define-constant ERR_INSUFFICIENT_BALANCE (err u802))
(define-constant ERR_INSUFFICIENT_CAPACITY (err u803))
(define-constant ERR_NO_DEPOSIT (err u804))
(define-constant ERR_TRANSFER_FAILED (err u805))
(define-constant ERR_INVALID_PARAMS (err u806))
(define-constant ERR_TOKEN_MISMATCH (err u807))
(define-constant ERR_COMMITMENT_NOT_FOUND (err u808))
(define-constant ERR_ZERO_PRICE (err u809))
(define-constant ERR_ASSET_NOT_FOUND (err u810))
(define-constant ERR_ASSET_NOT_ACTIVE (err u811))
(define-constant ERR_VAULT_PAUSED (err u812))
(define-constant ERR_RATE_TOO_HIGH (err u813))
(define-constant ERR_MAX_ASSETS (err u814))
(define-constant ERR_ASSET_IS_STX (err u815))
(define-constant ERR_ASSET_NOT_STX (err u816))

;; Asset type constants (configurable via configure-asset)
(define-constant ASSET_USDCX u0)
(define-constant ASSET_SBTC  u1)
(define-constant ASSET_STX   u2)
(define-constant ASSET_HUSD  u3)

;; Max supported assets
(define-constant MAX_ASSETS u10)

;; LTV denominator (basis points: 10000 = 100%)
(define-constant LTV_DENOMINATOR u10000)

;; Yield precision (10^12)
(define-constant PRECISION u1000000000000)

;; Price precision (6 decimals: $1.00 = u1000000)
(define-constant PRICE_PRECISION u1000000)

;; Minimum yield funding duration (~1 day in blocks)
(define-constant MIN_YIELD_DURATION u144)

;; ============================================
;; DATA VARIABLES
;; ============================================

(define-data-var admin principal CONTRACT_OWNER)
(define-data-var authorized-contracts (list 10 principal) (list))
(define-data-var vault-paused bool false)
(define-data-var asset-count uint u0)

;; ============================================
;; DATA MAPS
;; ============================================

;; Per-asset configuration and yield accumulator
(define-map supported-assets uint {
  token-principal: (optional principal),  ;; none for STX (native)
  ltv-ratio: uint,                        ;; basis points (9000 = 90%)
  price-usd: uint,                        ;; 6 decimal USD price
  decimals: uint,                         ;; token native decimals
  is-active: bool,
  strategy-name: (string-ascii 64),       ;; e.g. "Stable Yield via Granite"
  ;; Synthetix yield accumulator
  reward-per-token-stored: uint,
  last-update-block: uint,
  reward-rate: uint,
  reward-end-block: uint,
  total-deposited: uint
})

;; Per-user, per-asset balances and yield tracking
(define-map user-asset-deposits { user: principal, asset-type: uint } {
  deposited: uint,
  reward-per-token-paid: uint,
  rewards-earned: uint
})

;; Aggregate committed USD across all circles for a user
(define-map user-committed principal {
  total-committed-usd: uint
})

;; Per-circle commitment tracking
(define-map circle-commitments-v3 { user: principal, circle-id: uint } {
  commitment-usd: uint
})

;; ============================================
;; PRIVATE FUNCTIONS
;; ============================================

(define-private (min-uint (a uint) (b uint))
  (if (<= a b) a b)
)

(define-private (is-authorized-caller (caller principal))
  (or (is-eq caller (var-get admin))
      (is-some (index-of? (var-get authorized-contracts) caller)))
)

;; Get or create default user deposit for an asset
(define-private (get-or-create-user-deposit (user principal) (asset-type uint))
  (default-to {
    deposited: u0,
    reward-per-token-paid: (get-asset-rpt asset-type),
    rewards-earned: u0
  } (map-get? user-asset-deposits { user: user, asset-type: asset-type }))
)

;; Get asset's current reward-per-token-stored
(define-private (get-asset-rpt (asset-type uint))
  (match (map-get? supported-assets asset-type)
    asset (get reward-per-token-stored asset)
    u0
  )
)

;; Update per-asset yield accumulator
(define-private (update-asset-reward (asset-type uint))
  (match (map-get? supported-assets asset-type)
    asset (let (
      (current-block stacks-block-height)
      (applicable-block (min-uint current-block (get reward-end-block asset)))
      (total (get total-deposited asset))
      (stored (get reward-per-token-stored asset))
      (last-block (get last-update-block asset))
    )
      (if (and (> total u0) (> applicable-block last-block))
        (let (
          (elapsed (- applicable-block last-block))
          (new-rewards (* elapsed (get reward-rate asset)))
          (additional (/ (* new-rewards PRECISION) total))
        )
          (map-set supported-assets asset-type
            (merge asset {
              reward-per-token-stored: (+ stored additional),
              last-update-block: applicable-block
            })
          )
        )
        (map-set supported-assets asset-type
          (merge asset { last-update-block: applicable-block })
        )
      )
    )
    true
  )
)

;; Update user's reward snapshot for a specific asset
(define-private (update-user-asset-reward (user principal) (asset-type uint))
  (match (map-get? user-asset-deposits { user: user, asset-type: asset-type })
    dep (let (
      (deposited (get deposited dep))
      (paid (get reward-per-token-paid dep))
      (earned (get rewards-earned dep))
      (current-rpt (get-asset-rpt asset-type))
      (new-earned (+ earned (/ (* deposited (- current-rpt paid)) PRECISION)))
    )
      (map-set user-asset-deposits { user: user, asset-type: asset-type }
        (merge dep {
          rewards-earned: new-earned,
          reward-per-token-paid: current-rpt
        })
      )
    )
    true
  )
)

;; Calculate USD capacity for a single asset deposit
(define-private (calculate-asset-capacity-usd (asset-type uint) (deposited uint))
  (match (map-get? supported-assets asset-type)
    asset (let (
      (price (get price-usd asset))
      (decimals (get decimals asset))
      (ltv (get ltv-ratio asset))
      (usd-value (/ (* deposited price) (pow u10 decimals)))
    )
      (/ (* usd-value ltv) LTV_DENOMINATOR)
    )
    u0
  )
)

;; Sum capacity across N asset types (iterative via fold)
(define-private (sum-user-capacity (asset-type uint) (acc { user: principal, total: uint }))
  (let (
    (user (get user acc))
    (dep-data (default-to { deposited: u0, reward-per-token-paid: u0, rewards-earned: u0 }
      (map-get? user-asset-deposits { user: user, asset-type: asset-type })))
    (cap (calculate-asset-capacity-usd asset-type (get deposited dep-data)))
  )
    { user: user, total: (+ (get total acc) cap) }
  )
)

;; Check if withdrawal would leave enough capacity to cover commitments
(define-private (check-withdrawal-capacity (user principal) (withdraw-asset uint) (withdraw-amount uint))
  (let (
    ;; Calculate total capacity across all assets, adjusting for the withdrawal
    (result (fold sum-user-capacity
      (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9)
      { user: user, total: u0 }))
    (total-cap (get total result))
    ;; Subtract capacity being withdrawn
    (withdraw-cap (calculate-asset-capacity-usd withdraw-asset withdraw-amount))
    (new-cap (if (> total-cap withdraw-cap) (- total-cap withdraw-cap) u0))
    (committed (get total-committed-usd (default-to { total-committed-usd: u0 } (map-get? user-committed user))))
  )
    (asserts! (>= new-cap committed) ERR_INSUFFICIENT_CAPACITY)
    (ok true)
  )
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

(define-read-only (get-asset-config (asset-type uint))
  (map-get? supported-assets asset-type)
)

(define-read-only (get-user-deposit (user principal) (asset-type uint))
  (map-get? user-asset-deposits { user: user, asset-type: asset-type })
)

(define-read-only (get-user-committed (user principal))
  (default-to { total-committed-usd: u0 }
    (map-get? user-committed user))
)

(define-read-only (get-circle-commitment (user principal) (circle-id uint))
  (map-get? circle-commitments-v3 { user: user, circle-id: circle-id })
)

(define-read-only (get-total-capacity (user principal))
  (let (
    (result (fold sum-user-capacity
      (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9)
      { user: user, total: u0 }))
  )
    (ok (get total result))
  )
)

(define-read-only (get-available-capacity (user principal))
  (let (
    (total-cap (unwrap-panic (get-total-capacity user)))
    (committed (get total-committed-usd (get-user-committed user)))
  )
    (ok (if (> total-cap committed) (- total-cap committed) u0))
  )
)

(define-read-only (can-commit (user principal) (additional-usd uint))
  (let (
    (available (unwrap-panic (get-available-capacity user)))
  )
    (ok (>= available additional-usd))
  )
)

;; Get pending yield for a user on a specific asset (view, no state change)
(define-read-only (get-pending-yield (user principal) (asset-type uint))
  (match (map-get? user-asset-deposits { user: user, asset-type: asset-type })
    dep (match (map-get? supported-assets asset-type)
      asset (let (
        (deposited (get deposited dep))
        (paid (get reward-per-token-paid dep))
        (earned (get rewards-earned dep))
        (stored (get reward-per-token-stored asset))
        (total (get total-deposited asset))
        (current-block stacks-block-height)
        (applicable-block (min-uint current-block (get reward-end-block asset)))
        (last-block (get last-update-block asset))
        (current-rpt (if (and (> total u0) (> applicable-block last-block))
          (+ stored (/ (* (* (- applicable-block last-block) (get reward-rate asset)) PRECISION) total))
          stored
        ))
      )
        (+ earned (/ (* deposited (- current-rpt paid)) PRECISION))
      )
      u0
    )
    u0
  )
)

(define-read-only (get-admin)
  (var-get admin)
)

(define-read-only (get-asset-count)
  (var-get asset-count)
)

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

(define-read-only (is-authorized (caller principal))
  (is-authorized-caller caller)
)

;; Get vault summary for a user across all configured assets
(define-read-only (get-vault-summary (user principal))
  (let (
    (usdcx-dep (default-to { deposited: u0, reward-per-token-paid: u0, rewards-earned: u0 }
      (map-get? user-asset-deposits { user: user, asset-type: ASSET_USDCX })))
    (sbtc-dep (default-to { deposited: u0, reward-per-token-paid: u0, rewards-earned: u0 }
      (map-get? user-asset-deposits { user: user, asset-type: ASSET_SBTC })))
    (stx-dep (default-to { deposited: u0, reward-per-token-paid: u0, rewards-earned: u0 }
      (map-get? user-asset-deposits { user: user, asset-type: ASSET_STX })))
    (husd-dep (default-to { deposited: u0, reward-per-token-paid: u0, rewards-earned: u0 }
      (map-get? user-asset-deposits { user: user, asset-type: ASSET_HUSD })))
    (committed-data (get-user-committed user))
  )
    {
      usdcx-deposited: (get deposited usdcx-dep),
      sbtc-deposited: (get deposited sbtc-dep),
      stx-deposited: (get deposited stx-dep),
      husd-deposited: (get deposited husd-dep),
      total-committed-usd: (get total-committed-usd committed-data),
      total-capacity-usd: (unwrap-panic (get-total-capacity user)),
      available-capacity-usd: (unwrap-panic (get-available-capacity user))
    }
  )
)

;; ============================================
;; PUBLIC FUNCTIONS -- DEPOSITS
;; ============================================

;; Deposit SIP-010 token (USDCx, sBTC, hUSD)
(define-public (deposit-token (token <ft-trait>) (asset-type uint) (amount uint))
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets asset-type) ERR_ASSET_NOT_FOUND))
    (expected-token (unwrap! (get token-principal asset) ERR_ASSET_IS_STX))
  )
    (asserts! (get is-active asset) ERR_ASSET_NOT_ACTIVE)
    (asserts! (is-eq (contract-of token) expected-token) ERR_TOKEN_MISMATCH)
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)
    (asserts! (> (get price-usd asset) u0) ERR_ZERO_PRICE)

    ;; Update yield accumulators
    (update-asset-reward asset-type)
    (update-user-asset-reward caller asset-type)

    ;; Update user deposit (effects before interactions)
    (let (
      (dep (get-or-create-user-deposit caller asset-type))
    )
      (map-set user-asset-deposits { user: caller, asset-type: asset-type }
        (merge dep { deposited: (+ (get deposited dep) amount) })
      )
    )

    ;; Update asset total
    (map-set supported-assets asset-type
      (merge (unwrap-panic (map-get? supported-assets asset-type))
        { total-deposited: (+ (get total-deposited asset) amount) })
    )

    ;; Transfer tokens (interactions last)
    (try! (contract-call? token transfer amount caller (as-contract tx-sender) none))

    (print { event: "vault-v3-deposit", user: caller, asset-type: asset-type, amount: amount })
    (ok true)
  )
)

;; Deposit native STX
(define-public (deposit-stx (amount uint))
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets ASSET_STX) ERR_ASSET_NOT_FOUND))
  )
    (asserts! (get is-active asset) ERR_ASSET_NOT_ACTIVE)
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)
    (asserts! (> (get price-usd asset) u0) ERR_ZERO_PRICE)

    (update-asset-reward ASSET_STX)
    (update-user-asset-reward caller ASSET_STX)

    (let (
      (dep (get-or-create-user-deposit caller ASSET_STX))
    )
      (map-set user-asset-deposits { user: caller, asset-type: ASSET_STX }
        (merge dep { deposited: (+ (get deposited dep) amount) })
      )
    )

    (map-set supported-assets ASSET_STX
      (merge (unwrap-panic (map-get? supported-assets ASSET_STX))
        { total-deposited: (+ (get total-deposited asset) amount) })
    )

    (try! (stx-transfer? amount caller (as-contract tx-sender)))

    (print { event: "vault-v3-deposit", user: caller, asset-type: ASSET_STX, amount: amount })
    (ok true)
  )
)

;; ============================================
;; PUBLIC FUNCTIONS -- WITHDRAWALS
;; ============================================

;; Withdraw SIP-010 token
(define-public (withdraw-token (token <ft-trait>) (asset-type uint) (amount uint))
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets asset-type) ERR_ASSET_NOT_FOUND))
    (expected-token (unwrap! (get token-principal asset) ERR_ASSET_IS_STX))
  )
    (asserts! (is-eq (contract-of token) expected-token) ERR_TOKEN_MISMATCH)
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)

    (update-asset-reward asset-type)
    (update-user-asset-reward caller asset-type)

    ;; Check balance
    (let (
      (dep (unwrap! (map-get? user-asset-deposits { user: caller, asset-type: asset-type }) ERR_NO_DEPOSIT))
    )
      (asserts! (<= amount (get deposited dep)) ERR_INSUFFICIENT_BALANCE)
    )

    ;; Check withdrawal won't violate capacity constraints
    (try! (check-withdrawal-capacity caller asset-type amount))

    ;; Update deposit before transfer
    (let (
      (dep (unwrap-panic (map-get? user-asset-deposits { user: caller, asset-type: asset-type })))
    )
      (map-set user-asset-deposits { user: caller, asset-type: asset-type }
        (merge dep { deposited: (- (get deposited dep) amount) })
      )
    )

    ;; Update asset total
    (map-set supported-assets asset-type
      (merge (unwrap-panic (map-get? supported-assets asset-type))
        { total-deposited: (- (get total-deposited asset) amount) })
    )

    ;; Transfer (interactions last)
    (try! (as-contract (contract-call? token transfer amount tx-sender caller none)))

    (print { event: "vault-v3-withdraw", user: caller, asset-type: asset-type, amount: amount })
    (ok true)
  )
)

;; Withdraw native STX
(define-public (withdraw-stx (amount uint))
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets ASSET_STX) ERR_ASSET_NOT_FOUND))
  )
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (not (var-get vault-paused)) ERR_VAULT_PAUSED)

    (update-asset-reward ASSET_STX)
    (update-user-asset-reward caller ASSET_STX)

    (let (
      (dep (unwrap! (map-get? user-asset-deposits { user: caller, asset-type: ASSET_STX }) ERR_NO_DEPOSIT))
    )
      (asserts! (<= amount (get deposited dep)) ERR_INSUFFICIENT_BALANCE)
    )

    (try! (check-withdrawal-capacity caller ASSET_STX amount))

    (let (
      (dep (unwrap-panic (map-get? user-asset-deposits { user: caller, asset-type: ASSET_STX })))
    )
      (map-set user-asset-deposits { user: caller, asset-type: ASSET_STX }
        (merge dep { deposited: (- (get deposited dep) amount) })
      )
    )

    (map-set supported-assets ASSET_STX
      (merge (unwrap-panic (map-get? supported-assets ASSET_STX))
        { total-deposited: (- (get total-deposited asset) amount) })
    )

    (try! (as-contract (stx-transfer? amount tx-sender caller)))

    (print { event: "vault-v3-withdraw", user: caller, asset-type: ASSET_STX, amount: amount })
    (ok true)
  )
)

;; ============================================
;; PUBLIC FUNCTIONS -- YIELD CLAIMS
;; ============================================

;; Claim yield for SIP-010 asset
(define-public (claim-yield-token (token <ft-trait>) (asset-type uint))
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets asset-type) ERR_ASSET_NOT_FOUND))
    (expected-token (unwrap! (get token-principal asset) ERR_ASSET_IS_STX))
  )
    (asserts! (is-eq (contract-of token) expected-token) ERR_TOKEN_MISMATCH)
    (asserts! (get is-active asset) ERR_ASSET_NOT_ACTIVE)

    (update-asset-reward asset-type)
    (update-user-asset-reward caller asset-type)

    (let (
      (dep (unwrap! (map-get? user-asset-deposits { user: caller, asset-type: asset-type }) ERR_NO_DEPOSIT))
      (reward (get rewards-earned dep))
    )
      (asserts! (> reward u0) ERR_INVALID_AMOUNT)
      (try! (as-contract (contract-call? token transfer reward tx-sender caller none)))
      (map-set user-asset-deposits { user: caller, asset-type: asset-type }
        (merge dep { rewards-earned: u0 })
      )
      (print { event: "yield-claimed-v3", user: caller, asset-type: asset-type, amount: reward })
      (ok reward)
    )
  )
)

;; Claim yield for STX deposits
(define-public (claim-yield-stx)
  (let (
    (caller tx-sender)
    (asset (unwrap! (map-get? supported-assets ASSET_STX) ERR_ASSET_NOT_FOUND))
  )
    (asserts! (get is-active asset) ERR_ASSET_NOT_ACTIVE)

    (update-asset-reward ASSET_STX)
    (update-user-asset-reward caller ASSET_STX)

    (let (
      (dep (unwrap! (map-get? user-asset-deposits { user: caller, asset-type: ASSET_STX }) ERR_NO_DEPOSIT))
      (reward (get rewards-earned dep))
    )
      (asserts! (> reward u0) ERR_INVALID_AMOUNT)
      (try! (as-contract (stx-transfer? reward tx-sender caller)))
      (map-set user-asset-deposits { user: caller, asset-type: ASSET_STX }
        (merge dep { rewards-earned: u0 })
      )
      (print { event: "yield-claimed-v3", user: caller, asset-type: ASSET_STX, amount: reward })
      (ok reward)
    )
  )
)

;; ============================================
;; PUBLIC FUNCTIONS -- COLLATERAL (authorized contracts only)
;; ============================================

(define-public (lock-collateral (user principal) (circle-id uint) (commitment-usd uint))
  (let (
    (caller contract-caller)
  )
    (asserts! (is-authorized-caller caller) ERR_NOT_AUTHORIZED)
    (asserts! (> commitment-usd u0) ERR_INVALID_AMOUNT)

    (let (
      (total-cap (unwrap-panic (get-total-capacity user)))
      (committed-data (get-user-committed user))
      (current-committed (get total-committed-usd committed-data))
      (new-committed (+ current-committed commitment-usd))
    )
      (asserts! (<= new-committed total-cap) ERR_INSUFFICIENT_CAPACITY)

      (map-set user-committed user { total-committed-usd: new-committed })
      (map-set circle-commitments-v3 { user: user, circle-id: circle-id } {
        commitment-usd: commitment-usd
      })

      (print {
        event: "collateral-locked-v3",
        user: user,
        circle-id: circle-id,
        commitment-usd: commitment-usd,
        total-committed: new-committed
      })

      (ok true)
    )
  )
)

(define-public (release-collateral (user principal) (circle-id uint))
  (let (
    (caller contract-caller)
  )
    (asserts! (is-authorized-caller caller) ERR_NOT_AUTHORIZED)

    (let (
      (commitment (unwrap! (map-get? circle-commitments-v3 { user: user, circle-id: circle-id })
                           ERR_COMMITMENT_NOT_FOUND))
      (commitment-usd (get commitment-usd commitment))
      (committed-data (get-user-committed user))
      (current-committed (get total-committed-usd committed-data))
      (new-committed (if (> current-committed commitment-usd)
                        (- current-committed commitment-usd)
                        u0))
    )
      (map-set user-committed user { total-committed-usd: new-committed })
      (map-delete circle-commitments-v3 { user: user, circle-id: circle-id })

      (print {
        event: "collateral-released-v3",
        user: user,
        circle-id: circle-id,
        released-usd: commitment-usd
      })

      (ok true)
    )
  )
)

(define-public (slash-collateral (user principal) (circle-id uint) (slash-usd uint))
  (let (
    (caller contract-caller)
  )
    (asserts! (is-authorized-caller caller) ERR_NOT_AUTHORIZED)
    (asserts! (> slash-usd u0) ERR_INVALID_AMOUNT)

    ;; Release the circle commitment
    (let (
      (commitment (unwrap! (map-get? circle-commitments-v3 { user: user, circle-id: circle-id })
                           ERR_COMMITMENT_NOT_FOUND))
      (commitment-usd (get commitment-usd commitment))
      (committed-data (get-user-committed user))
      (current-committed (get total-committed-usd committed-data))
      (new-committed (if (> current-committed commitment-usd)
                        (- current-committed commitment-usd)
                        u0))
    )
      (map-set user-committed user { total-committed-usd: new-committed })
      (map-delete circle-commitments-v3 { user: user, circle-id: circle-id })

      (print {
        event: "collateral-slashed-v3",
        user: user,
        circle-id: circle-id,
        slash-usd: slash-usd
      })

      (ok true)
    )
  )
)

;; ============================================
;; ADMIN FUNCTIONS
;; ============================================

;; Configure or update an asset type
(define-public (configure-asset
  (asset-type uint)
  (token-principal (optional principal))
  (ltv-ratio uint)
  (price-usd uint)
  (decimals uint)
  (strategy-name (string-ascii 64))
)
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (asserts! (< asset-type MAX_ASSETS) ERR_MAX_ASSETS)
    (asserts! (<= ltv-ratio LTV_DENOMINATOR) ERR_INVALID_PARAMS)
    (asserts! (> price-usd u0) ERR_ZERO_PRICE)

    ;; Track new assets
    (if (is-none (map-get? supported-assets asset-type))
      (var-set asset-count (+ (var-get asset-count) u1))
      true
    )

    (map-set supported-assets asset-type {
      token-principal: token-principal,
      ltv-ratio: ltv-ratio,
      price-usd: price-usd,
      decimals: decimals,
      is-active: true,
      strategy-name: strategy-name,
      reward-per-token-stored: u0,
      last-update-block: stacks-block-height,
      reward-rate: u0,
      reward-end-block: u0,
      total-deposited: u0
    })

    (print { event: "asset-configured", asset-type: asset-type, ltv-ratio: ltv-ratio, strategy-name: strategy-name })
    (ok true)
  )
)

;; Update asset price (oracle)
(define-public (set-asset-price (asset-type uint) (price-usd uint))
  (let (
    (asset (unwrap! (map-get? supported-assets asset-type) ERR_ASSET_NOT_FOUND))
  )
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (asserts! (> price-usd u0) ERR_ZERO_PRICE)
    (map-set supported-assets asset-type
      (merge asset { price-usd: price-usd })
    )
    (print { event: "price-updated", asset-type: asset-type, price-usd: price-usd })
    (ok true)
  )
)

;; Fund yield for SIP-010 asset (distribute real earned yield)
(define-public (fund-yield-token (token <ft-trait>) (asset-type uint) (amount uint) (duration-blocks uint))
  (let (
    (asset (unwrap! (map-get? supported-assets asset-type) ERR_ASSET_NOT_FOUND))
    (expected-token (unwrap! (get token-principal asset) ERR_ASSET_IS_STX))
  )
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (asserts! (is-eq (contract-of token) expected-token) ERR_TOKEN_MISMATCH)
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (>= duration-blocks MIN_YIELD_DURATION) ERR_INVALID_PARAMS)

    (update-asset-reward asset-type)

    (let (
      (current-block stacks-block-height)
      (remaining (if (> (get reward-end-block asset) current-block)
                    (* (get reward-rate asset) (- (get reward-end-block asset) current-block))
                    u0))
      (total-reward (+ amount remaining))
      (new-rate (/ total-reward duration-blocks))
    )
      (asserts! (<= new-rate (* amount u10)) ERR_RATE_TOO_HIGH)

      (try! (contract-call? token transfer amount tx-sender (as-contract tx-sender) none))

      (map-set supported-assets asset-type
        (merge (unwrap-panic (map-get? supported-assets asset-type)) {
          reward-rate: new-rate,
          reward-end-block: (+ current-block duration-blocks),
          last-update-block: current-block
        })
      )

      (print { event: "yield-funded-v3", asset-type: asset-type, amount: amount, duration: duration-blocks, rate: new-rate })
      (ok true)
    )
  )
)

;; Fund yield for STX
(define-public (fund-yield-stx (amount uint) (duration-blocks uint))
  (let (
    (asset (unwrap! (map-get? supported-assets ASSET_STX) ERR_ASSET_NOT_FOUND))
  )
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (asserts! (> amount u0) ERR_INVALID_AMOUNT)
    (asserts! (>= duration-blocks MIN_YIELD_DURATION) ERR_INVALID_PARAMS)

    (update-asset-reward ASSET_STX)

    (let (
      (current-block stacks-block-height)
      (remaining (if (> (get reward-end-block asset) current-block)
                    (* (get reward-rate asset) (- (get reward-end-block asset) current-block))
                    u0))
      (total-reward (+ amount remaining))
      (new-rate (/ total-reward duration-blocks))
    )
      (asserts! (<= new-rate (* amount u10)) ERR_RATE_TOO_HIGH)

      (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

      (map-set supported-assets ASSET_STX
        (merge (unwrap-panic (map-get? supported-assets ASSET_STX)) {
          reward-rate: new-rate,
          reward-end-block: (+ current-block duration-blocks),
          last-update-block: current-block
        })
      )

      (print { event: "yield-funded-v3", asset-type: ASSET_STX, amount: amount, duration: duration-blocks, rate: new-rate })
      (ok true)
    )
  )
)

;; Authorize a contract to lock/release/slash collateral
(define-public (authorize-contract (contract principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (var-set authorized-contracts
      (unwrap! (as-max-len? (append (var-get authorized-contracts) contract) u10) ERR_INVALID_PARAMS))
    (print { event: "contract-authorized", contract: contract })
    (ok true)
  )
)

;; Pause/unpause vault
(define-public (pause-vault)
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (var-set vault-paused true)
    (print { event: "vault-paused" })
    (ok true)
  )
)

(define-public (unpause-vault)
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (var-set vault-paused false)
    (print { event: "vault-unpaused" })
    (ok true)
  )
)

;; Transfer admin
(define-public (set-admin (new-admin principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_NOT_AUTHORIZED)
    (var-set admin new-admin)
    (print { event: "admin-transferred", new-admin: new-admin })
    (ok true)
  )
)

Functions (39)

FunctionAccessArgs
min-uintprivatea: uint, b: uint
is-authorized-callerprivatecaller: principal
get-or-create-user-depositprivateuser: principal, asset-type: uint
get-asset-rptprivateasset-type: uint
update-asset-rewardprivateasset-type: uint
update-user-asset-rewardprivateuser: principal, asset-type: uint
calculate-asset-capacity-usdprivateasset-type: uint, deposited: uint
sum-user-capacityprivateasset-type: uint, acc: { user: principal, total: uint }
check-withdrawal-capacityprivateuser: principal, withdraw-asset: uint, withdraw-amount: uint
get-asset-configread-onlyasset-type: uint
get-user-depositread-onlyuser: principal, asset-type: uint
get-user-committedread-onlyuser: principal
get-circle-commitmentread-onlyuser: principal, circle-id: uint
get-total-capacityread-onlyuser: principal
get-available-capacityread-onlyuser: principal
can-commitread-onlyuser: principal, additional-usd: uint
get-pending-yieldread-onlyuser: principal, asset-type: uint
get-adminread-only
get-asset-countread-only
is-pausedread-only
is-authorizedread-onlycaller: principal
get-vault-summaryread-onlyuser: principal
deposit-tokenpublictoken: <ft-trait>, asset-type: uint, amount: uint
deposit-stxpublicamount: uint
withdraw-tokenpublictoken: <ft-trait>, asset-type: uint, amount: uint
withdraw-stxpublicamount: uint
claim-yield-tokenpublictoken: <ft-trait>, asset-type: uint
claim-yield-stxpublic
lock-collateralpublicuser: principal, circle-id: uint, commitment-usd: uint
release-collateralpublicuser: principal, circle-id: uint
slash-collateralpublicuser: principal, circle-id: uint, slash-usd: uint
configure-assetpublicasset-type: uint, token-principal: (optional principal
set-asset-pricepublicasset-type: uint, price-usd: uint
fund-yield-tokenpublictoken: <ft-trait>, asset-type: uint, amount: uint, duration-blocks: uint
fund-yield-stxpublicamount: uint, duration-blocks: uint
authorize-contractpubliccontract: principal
pause-vaultpublic
unpause-vaultpublic
set-adminpublicnew-admin: principal