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-fund-inactive (err u105))

(define-data-var etf-nonce uint u0)

(define-map etf-funds
  uint
  {
    fund-manager: principal,
    fund-name: (string-ascii 50),
    fund-symbol: (string-ascii 10),
    total-shares: uint,
    nav-per-share: uint,
    management-fee: uint,
    active: bool,
    total-aum: uint
  }
)

(define-map fund-holdings
  {fund-id: uint, asset-id: uint}
  {
    asset-symbol: (string-ascii 10),
    quantity: uint,
    value: uint,
    weight-percentage: uint
  }
)

(define-map shareholder-positions
  {fund-id: uint, shareholder: principal}
  {
    shares-owned: uint,
    cost-basis: uint,
    purchase-block: uint
  }
)

(define-map manager-funds principal (list 20 uint))
(define-map fund-shareholders uint (list 500 principal))

(define-public (create-etf (fund-name (string-ascii 50)) (fund-symbol (string-ascii 10)) (initial-shares uint) (nav-per-share uint) (management-fee uint))
  (let
    (
      (fund-id (+ (var-get etf-nonce) u1))
    )
    (asserts! (> initial-shares u0) err-invalid-amount)
    (asserts! (> nav-per-share u0) err-invalid-amount)
    (asserts! (<= management-fee u1000) err-invalid-amount)
    (map-set etf-funds fund-id
      {
        fund-manager: tx-sender,
        fund-name: fund-name,
        fund-symbol: fund-symbol,
        total-shares: initial-shares,
        nav-per-share: nav-per-share,
        management-fee: management-fee,
        active: true,
        total-aum: (* initial-shares nav-per-share)
      }
    )
    (map-set shareholder-positions {fund-id: fund-id, shareholder: tx-sender}
      {
        shares-owned: initial-shares,
        cost-basis: nav-per-share,
        purchase-block: stacks-block-height
      }
    )
    (map-set manager-funds tx-sender
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? manager-funds tx-sender)) fund-id) u20)))
    (var-set etf-nonce fund-id)
    (ok fund-id)
  )
)

(define-public (purchase-etf-shares (fund-id uint) (shares uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
      (position (default-to {shares-owned: u0, cost-basis: u0, purchase-block: u0} (map-get? shareholder-positions {fund-id: fund-id, shareholder: tx-sender})))
      (total-cost (* shares (get nav-per-share fund)))
    )
    (asserts! (get active fund) err-fund-inactive)
    (asserts! (> shares u0) err-invalid-amount)
    (try! (stx-transfer? total-cost tx-sender (as-contract tx-sender)))
    (map-set shareholder-positions {fund-id: fund-id, shareholder: tx-sender}
      {
        shares-owned: (+ (get shares-owned position) shares),
        cost-basis: (get nav-per-share fund),
        purchase-block: stacks-block-height
      }
    )
    (map-set etf-funds fund-id (merge fund {
      total-shares: (+ (get total-shares fund) shares),
      total-aum: (+ (get total-aum fund) total-cost)
    }))
    (ok true)
  )
)

(define-public (redeem-etf-shares (fund-id uint) (shares uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
      (position (unwrap! (map-get? shareholder-positions {fund-id: fund-id, shareholder: tx-sender}) err-not-found))
      (redemption-value (* shares (get nav-per-share fund)))
    )
    (asserts! (get active fund) err-fund-inactive)
    (asserts! (>= (get shares-owned position) shares) err-invalid-amount)
    (try! (as-contract (stx-transfer? redemption-value tx-sender tx-sender)))
    (map-set shareholder-positions {fund-id: fund-id, shareholder: tx-sender}
      (merge position {shares-owned: (- (get shares-owned position) shares)}))
    (map-set etf-funds fund-id (merge fund {
      total-shares: (- (get total-shares fund) shares),
      total-aum: (- (get total-aum fund) redemption-value)
    }))
    (ok redemption-value)
  )
)

(define-public (add-fund-holding (fund-id uint) (asset-id uint) (asset-symbol (string-ascii 10)) (quantity uint) (value uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
      (weight-pct (if (> (get total-aum fund) u0) (/ (* value u10000) (get total-aum fund)) u0))
    )
    (asserts! (is-eq tx-sender (get fund-manager fund)) err-unauthorized)
    (map-set fund-holdings {fund-id: fund-id, asset-id: asset-id}
      {
        asset-symbol: asset-symbol,
        quantity: quantity,
        value: value,
        weight-percentage: weight-pct
      }
    )
    (ok true)
  )
)

(define-public (update-nav (fund-id uint) (new-nav uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get fund-manager fund)) err-unauthorized)
    (asserts! (> new-nav u0) err-invalid-amount)
    (map-set etf-funds fund-id (merge fund {
      nav-per-share: new-nav,
      total-aum: (* (get total-shares fund) new-nav)
    }))
    (ok true)
  )
)

(define-public (collect-management-fee (fund-id uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
      (fee-amount (/ (* (get total-aum fund) (get management-fee fund)) u10000))
    )
    (asserts! (is-eq tx-sender (get fund-manager fund)) err-unauthorized)
    (try! (as-contract (stx-transfer? fee-amount tx-sender (get fund-manager fund))))
    (ok fee-amount)
  )
)

(define-public (deactivate-fund (fund-id uint))
  (let
    (
      (fund (unwrap! (map-get? etf-funds fund-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get fund-manager fund)) err-unauthorized)
    (map-set etf-funds fund-id (merge fund {active: false}))
    (ok true)
  )
)

(define-read-only (get-etf-fund (fund-id uint))
  (ok (map-get? etf-funds fund-id))
)

(define-read-only (get-fund-holding (fund-id uint) (asset-id uint))
  (ok (map-get? fund-holdings {fund-id: fund-id, asset-id: asset-id}))
)

(define-read-only (get-shareholder-position (fund-id uint) (shareholder principal))
  (ok (map-get? shareholder-positions {fund-id: fund-id, shareholder: shareholder}))
)

(define-read-only (get-manager-funds (manager principal))
  (ok (map-get? manager-funds manager))
)

Functions (11)

FunctionAccessArgs
create-etfpublicfund-name: (string-ascii 50
purchase-etf-sharespublicfund-id: uint, shares: uint
redeem-etf-sharespublicfund-id: uint, shares: uint
add-fund-holdingpublicfund-id: uint, asset-id: uint, asset-symbol: (string-ascii 10
update-navpublicfund-id: uint, new-nav: uint
collect-management-feepublicfund-id: uint
deactivate-fundpublicfund-id: uint
get-etf-fundread-onlyfund-id: uint
get-fund-holdingread-onlyfund-id: uint, asset-id: uint
get-shareholder-positionread-onlyfund-id: uint, shareholder: principal
get-manager-fundsread-onlymanager: principal