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