Source Code

;; Property Registry Smart Contract

;; CONTRACT OWNER
(define-constant CONTRACT_OWNER tx-sender)

;; ERROR CODES
(define-constant ERR_NOT_AUTHORIZED (err u1001))
(define-constant ERR_PROPERTY_NOT_FOUND (err u1002))
(define-constant ERR_INVALID_AMOUNT (err u1003))
(define-constant ERR_PROPERTY_ALREADY_EXISTS (err u1004))
(define-constant ERR_INVALID_INPUT (err u1005))
(define-constant ERR_PROPERTY_VALUE_TOO_HIGH (err u1006))
(define-constant ERR_RENT_YIELD_UNREALISTIC (err u1007))
(define-constant ERR_ALREADY_VOTED (err u1009))
(define-constant ERR_PROPOSAL_EXPIRED (err u1010))
(define-constant ERR_NO_INVESTMENT (err u1011))
(define-constant ERR_CONTRACT_PAUSED (err u1012))
(define-constant ERR_INVESTOR_NOT_WHITELISTED (err u1013))
(define-constant ERR_INVESTOR_BLACKLISTED (err u1014))
(define-constant ERR_TOO_MANY_INVESTORS (err u1015))
(define-constant ERR_PROPOSAL_NOT_PASSED (err u1018))
(define-constant ERR_PROPOSAL_ALREADY_EXECUTED (err u1019))
(define-constant ERR_LISTING_NOT_FOUND (err u1020))
(define-constant ERR_SHARES_NOT_AVAILABLE (err u1021))
(define-constant ERR_PROPERTY_NOT_LIQUIDATED (err u1022))
(define-constant ERR_INSUFFICIENT_VOTES (err u1023))
(define-constant ERR_FUNDING_INCOMPLETE (err u1024))
(define-constant ERR_ALREADY_ACTIVATED (err u1025))
(define-constant ERR_FUNDS_ALREADY_RELEASED (err u1026))
(define-constant ERR_INSUFFICIENT_SHARES (err u1027))
(define-constant ERR_ARITHMETIC_OVERFLOW (err u1028))
(define-constant ERR_DEADLINE_ALREADY_CHECKED (err u1029))
(define-constant ERR_UNAUTHORIZED_CALLER (err u1030))
(define-constant ERR_BALANCE_CHECK_FAILED (err u1031))
(define-constant ERR_LIQUIDATION_TOO_LOW (err u1032))
(define-constant ERR_GOVERNANCE_ACTION_NOT_EXECUTED (err u1033))
(define-constant ERR_VERIFICATION_SCORE_TOO_LOW (err u1034))

;; CONSTANTS & CONFIGURATION
(define-constant CONTRACT_VERSION u2)
(define-constant MAX_PROPERTY_VALUE u1000000000)
(define-constant MIN_PROPERTY_VALUE u10000000)
(define-constant TARGET_ANNUAL_YIELD u2000)
(define-constant MIN_FUNDING_THRESHOLD u5000)
(define-constant MAX_FUNDING_THRESHOLD u10000)
(define-constant VOTING_PERIOD u1440)
(define-constant MAX_INVESTORS_PER_PROPERTY u100)
(define-constant PROPOSAL_EXECUTION_DELAY u144)
(define-constant PROPOSAL_EXPIRY_PERIOD u8640)
(define-constant QUORUM_PERCENTAGE u3000)
(define-constant MIN_PARTICIPATION_PCT u5000)
(define-constant FUNDS_RELEASE_DELAY u1440)
(define-constant MIN_LIQUIDATION_PCT u5000)
(define-constant SBTC_CONTRACT 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token)
(define-constant BASIS_POINTS_SCALE u10000)
(define-constant BLOCKS_PER_DAY u144)
(define-constant MIN_VERIFICATION_SCORE u85)

;; STATE VARIABLES
(define-data-var property-counter uint u0)
(define-data-var platform-fee-rate uint u300)
(define-data-var proposal-counter uint u0)
(define-data-var contract-paused bool false)

;; DATA MAPS
(define-map properties
  { property-id: uint }
  { 
    owner: principal,
    title: (string-ascii 100),
    location: (string-ascii 100),
    total-value-sbtc: uint,
    total-invested-sbtc: uint,
    monthly-rent-sbtc: uint,
    min-investment-sbtc: uint,
    is-verified: bool,
    is-active: bool,
    created-at: uint,
    funding-deadline: uint,
    funding-threshold: uint,
    funding-status: (string-ascii 20),
    is-liquidated: bool,
    liquidation-value: uint,
    funds-released: bool,
    funds-release-date: uint,
    deadline-checked: bool,
    verification-score: uint,
    verified-by-governance: bool
  })

(define-map whitelisted-investors principal bool)
(define-map blacklisted-investors principal bool)

(define-map governance-proposals
  { proposal-id: uint }
  { 
    property-id: uint,
    proposal-type: (string-ascii 50),
    description: (string-ascii 300),
    proposed-by: principal,
    created-at: uint,
    voting-deadline: uint,
    votes-for: uint,
    votes-against: uint,
    total-voting-power: uint,
    executed: bool,
    execution-block: uint,
    new-value: uint,
    new-principal: (optional principal),
    snapshot-total-investment: uint
  })

(define-map proposal-votes
  { proposal-id: uint, voter: principal }
  { 
    vote-weight: uint,
    vote-for: bool
  })

(define-map share-listings
  { property-id: uint, seller: principal }
  { 
    shares-for-sale: uint,
    price-per-share: uint,
    is-active: bool,
    shares-locked: uint,
    listing-date: uint,
    version: uint
  })

(define-map liquidation-distributions
  { property-id: uint }
  {
    total-distributed: uint,
    distribution-complete: bool,
    distribution-date: uint
  })

(define-map liquidation-claims
  { property-id: uint, investor: principal }
  {
    claimed: bool,
    amount-claimed: uint,
    claim-date: uint
  })

(define-map executed-governance-actions
  { action-id: uint }
  {
    executed-at: uint,
    executed-by: principal,
    property-affected: (optional uint),
    investor-affected: (optional principal)
  })

;; Safe arithmetic operations
(define-private (safe-add (a uint) (b uint))
  (let ((result (+ a b)))
    (if (and (>= result a) (>= result b))
      (ok result)
      ERR_ARITHMETIC_OVERFLOW)))

(define-private (safe-sub (a uint) (b uint))
  (if (>= a b)
    (ok (- a b))
    ERR_ARITHMETIC_OVERFLOW))

(define-private (safe-mul (a uint) (b uint))
  (if (is-eq b u0)
    (ok u0)
    (let ((result (* a b)))
      (if (is-eq (/ result b) a)
        (ok result)
        ERR_ARITHMETIC_OVERFLOW))))

(define-private (safe-div (a uint) (b uint))
  (if (> b u0)
    (ok (/ a b))
    ERR_INVALID_INPUT))

;; Use data-store-v3 for investment data
(define-read-only (get-property-investment-totals-public (property-id uint))
  (contract-call? .data-store-v3 get-property-investment-totals property-id))

(define-read-only (get-user-investment-public (property-id uint) (investor principal))
  (contract-call? .data-store-v3 get-user-investment property-id investor))

;; PRIVATE HELPER FUNCTIONS
(define-private (is-valid-property-value (value uint)) 
  (and (>= value MIN_PROPERTY_VALUE) (<= value MAX_PROPERTY_VALUE)))

(define-private (calculate-target-monthly-rent (property-value uint)) 
  (unwrap-panic (safe-div (unwrap-panic (safe-mul property-value TARGET_ANNUAL_YIELD)) 
                          (unwrap-panic (safe-mul u12 BASIS_POINTS_SCALE)))))

(define-private (is-realistic-rent-yield (monthly-rent uint) (property-value uint))
  (let ((annual-yield (unwrap-panic (safe-div (unwrap-panic (safe-mul (unwrap-panic (safe-mul monthly-rent u12)) BASIS_POINTS_SCALE)) 
                                               property-value))))
    (and (>= annual-yield u1500) (<= annual-yield u2500))))

(define-private (is-authorized-contract (caller principal))
  (or (is-eq caller .investment-manager-v3)
      (is-eq caller .rental-distributor-v3)))

;; GOVERNANCE HELPERS
(define-private (is-governance-admin (caller principal))
  (contract-call? .governance-v3 is-admin caller))

(define-private (is-admin-or-owner (caller principal))
  (or (is-eq caller CONTRACT_OWNER)
      (is-governance-admin caller)))

(define-private (check-emergency-status)
  (let ((emergency-stats (contract-call? .governance-v3 get-emergency-stats)))
    (if (and (> (get last-emergency emergency-stats) u0)
             (< (- stacks-block-height (get last-emergency emergency-stats)) u1440))
      ERR_CONTRACT_PAUSED
      (ok true))))

;; READ-ONLY FUNCTIONS
(define-read-only (is-contract-paused) 
  (var-get contract-paused))

(define-read-only (get-property (property-id uint)) 
  (map-get? properties { property-id: property-id }))

(define-read-only (get-property-count) 
  (var-get property-counter))

(define-read-only (get-platform-fee-rate) 
  (var-get platform-fee-rate))

(define-read-only (is-contract-owner (user principal)) 
  (is-eq user CONTRACT_OWNER))

(define-read-only (is-whitelisted (investor principal)) 
  (default-to false (map-get? whitelisted-investors investor)))

(define-read-only (is-blacklisted (investor principal)) 
  (default-to false (map-get? blacklisted-investors investor)))

(define-read-only (get-proposal (proposal-id uint)) 
  (map-get? governance-proposals { proposal-id: proposal-id }))

(define-read-only (get-user-vote (proposal-id uint) (voter principal)) 
  (map-get? proposal-votes { proposal-id: proposal-id, voter: voter }))

(define-read-only (get-share-listing (property-id uint) (seller principal)) 
  (map-get? share-listings { property-id: property-id, seller: seller }))

(define-read-only (get-executed-governance-action (action-id uint))
  (map-get? executed-governance-actions { action-id: action-id }))

(define-read-only (get-funding-info (property-id uint))
  (let (
    (property (unwrap! 
      (get-property property-id) 
      { funding-deadline: u0, funding-threshold: u0, funding-status: "not-found", 
        current-funding: u0, funding-percentage: u0, blocks-remaining: u0 }))
  )
    (let (
      (current-funding (get total-invested-sbtc property))
      (funding-percentage (if (> (get total-value-sbtc property) u0)
                            (unwrap-panic (safe-div (unwrap-panic (safe-mul current-funding BASIS_POINTS_SCALE)) 
                                                     (get total-value-sbtc property)))
                            u0))
      (blocks-remaining (if (> (get funding-deadline property) stacks-block-height)
                          (- (get funding-deadline property) stacks-block-height) 
                          u0))
    )
      { 
        funding-deadline: (get funding-deadline property), 
        funding-threshold: (get funding-threshold property),
        funding-status: (get funding-status property), 
        current-funding: current-funding,
        funding-percentage: funding-percentage, 
        blocks-remaining: blocks-remaining 
      })))

(define-read-only (can-execute-proposal (proposal-id uint))
  (let ((proposal (unwrap! (get-proposal proposal-id) (ok false))))
    (let (
      (snapshot-investment (get snapshot-total-investment proposal))
      (required-quorum (unwrap-panic (safe-div (unwrap-panic (safe-mul snapshot-investment QUORUM_PERCENTAGE)) BASIS_POINTS_SCALE)))
      (required-participation (unwrap-panic (safe-div (unwrap-panic (safe-mul snapshot-investment MIN_PARTICIPATION_PCT)) BASIS_POINTS_SCALE)))
      (voting-ended (> stacks-block-height (get voting-deadline proposal)))
      (execution-delay-passed (> stacks-block-height (+ (get voting-deadline proposal) PROPOSAL_EXECUTION_DELAY)))
      (not-expired (< stacks-block-height (+ (get voting-deadline proposal) PROPOSAL_EXPIRY_PERIOD)))
      (has-enough-votes (and 
        (> (get votes-for proposal) (get votes-against proposal))
        (or (is-eq snapshot-investment u0)
            (and (>= (get total-voting-power proposal) required-quorum)
                 (>= (get total-voting-power proposal) required-participation)))))
    )
      (ok (and 
        (not (get executed proposal))
        voting-ended
        execution-delay-passed
        not-expired
        has-enough-votes)))))

(define-read-only (get-available-shares-for-listing (property-id uint) (seller principal))
  (let ((listing (get-share-listing property-id seller)))
    (match listing
      list-data (ok (get shares-locked list-data))
      (ok u0))))

;; ADMIN FUNCTIONS
(define-public (pause-contract)
  (begin 
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED) 
    (var-set contract-paused true)
    (print { e: "paused", by: tx-sender })
    (ok true)))

(define-public (unpause-contract)
  (begin 
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED) 
    (var-set contract-paused false)
    (print { e: "unpaused", by: tx-sender })
    (ok true)))

;; GOVERNANCE INTEGRATED FUNCTIONS
(define-public (execute-governance-action (action-id uint))
  (let (
    (action (unwrap! (contract-call? .governance-v3 execute-action action-id) ERR_GOVERNANCE_ACTION_NOT_EXECUTED))
  )
    (asserts! (is-governance-admin tx-sender) ERR_NOT_AUTHORIZED)
    
    (let (
      (action-type (get action-type action))
      (target-contract (get target-contract action))
    )
      (asserts! (is-eq target-contract "property-registry-v3") ERR_INVALID_INPUT)
      
      (let ((result
        (if (is-eq action-type "verify-property")
          (execute-verify-property-action action)
          (if (is-eq action-type "update-platform-fee")
            (execute-fee-update-action action)
            (if (is-eq action-type "whitelist-investor")
              (execute-whitelist-action action)
              (if (is-eq action-type "blacklist-investor")
                (execute-blacklist-action action)
                ERR_INVALID_INPUT))))))
        
        (unwrap! result ERR_INVALID_INPUT)
        
        (map-set executed-governance-actions
          { action-id: action-id }
          {
            executed-at: stacks-block-height,
            executed-by: tx-sender,
            property-affected: (some (get value-1 action)),
            investor-affected: (get target-principal action)
          })
        
        (print { e: "gov-action-executed", id: action-id, type: action-type })
        (ok true)))))

(define-private (execute-verify-property-action (action {
    action-type: (string-ascii 50),
    description: (string-ascii 300),
    target-contract: (string-ascii 50),
    target-principal: (optional principal),
    value-1: uint,
    value-2: uint,
    value-3: uint,
    string-param: (optional (string-ascii 200)),
    created-at: uint,
    created-by: principal,
    executable-after: uint,
    expires-at: uint,
    executed: bool,
    executed-at: uint,
    approvals: (list 10 principal),
    approval-count: uint
  }))
  (let (
    (property-id (get value-1 action))
    (property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND))
    (verification-data (contract-call? .governance-v3 get-property-verification-score property-id))
  )
    (match verification-data
      score-data
        (begin
          (asserts! (>= (get total-score score-data) MIN_VERIFICATION_SCORE) ERR_VERIFICATION_SCORE_TOO_LOW)
          (asserts! (not (get is-verified property)) ERR_PROPERTY_ALREADY_EXISTS)
          
          (map-set properties 
            { property-id: property-id }
            (merge property { 
              is-verified: true, 
              is-active: true, 
              funding-status: "active",
              verification-score: (get total-score score-data),
              verified-by-governance: true
            }))
          
          (print { e: "verify", p: property-id, score: (get total-score score-data) })
          (ok true))
      ERR_VERIFICATION_SCORE_TOO_LOW)))

(define-private (execute-fee-update-action (action {
    action-type: (string-ascii 50),
    description: (string-ascii 300),
    target-contract: (string-ascii 50),
    target-principal: (optional principal),
    value-1: uint,
    value-2: uint,
    value-3: uint,
    string-param: (optional (string-ascii 200)),
    created-at: uint,
    created-by: principal,
    executable-after: uint,
    expires-at: uint,
    executed: bool,
    executed-at: uint,
    approvals: (list 10 principal),
    approval-count: uint
  }))
  (let ((new-rate (get value-1 action)))
    (asserts! (<= new-rate u1000) ERR_INVALID_AMOUNT)
    (var-set platform-fee-rate new-rate)
    (print { e: "fee-update", r: new-rate })
    (ok true)))

(define-private (execute-whitelist-action (action {
    action-type: (string-ascii 50),
    description: (string-ascii 300),
    target-contract: (string-ascii 50),
    target-principal: (optional principal),
    value-1: uint,
    value-2: uint,
    value-3: uint,
    string-param: (optional (string-ascii 200)),
    created-at: uint,
    created-by: principal,
    executable-after: uint,
    expires-at: uint,
    executed: bool,
    executed-at: uint,
    approvals: (list 10 principal),
    approval-count: uint
  }))
  (let ((investor (unwrap! (get target-principal action) ERR_INVALID_INPUT)))
    (asserts! (is-standard investor) ERR_INVALID_INPUT)
    (map-set whitelisted-investors investor true)
    (print { e: "whitelist", i: investor })
    (ok true)))

(define-private (execute-blacklist-action (action {
    action-type: (string-ascii 50),
    description: (string-ascii 300),
    target-contract: (string-ascii 50),
    target-principal: (optional principal),
    value-1: uint,
    value-2: uint,
    value-3: uint,
    string-param: (optional (string-ascii 200)),
    created-at: uint,
    created-by: principal,
    executable-after: uint,
    expires-at: uint,
    executed: bool,
    executed-at: uint,
    approvals: (list 10 principal),
    approval-count: uint
  }))
  (let ((investor (unwrap! (get target-principal action) ERR_INVALID_INPUT)))
    (asserts! (is-standard investor) ERR_INVALID_INPUT)
    (map-set blacklisted-investors investor true)
    (map-set whitelisted-investors investor false)
    (print { e: "blacklist", i: investor })
    (ok true)))

;; Legacy admin functions
(define-public (whitelist-investor (investor principal))
  (begin
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED)
    (asserts! (is-standard investor) ERR_INVALID_INPUT)
    (map-set whitelisted-investors investor true)
    (print { e: "whitelist", i: investor })
    (ok true)))

(define-public (blacklist-investor (investor principal))
  (begin
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED)
    (asserts! (is-standard investor) ERR_INVALID_INPUT)
    (map-set blacklisted-investors investor true)
    (print { e: "blacklist", i: investor })
    (ok true)))

(define-public (update-platform-fee-rate (new-rate uint))
  (begin
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED)
    (asserts! (<= new-rate u1000) ERR_INVALID_AMOUNT)
    (var-set platform-fee-rate new-rate)
    (print { e: "fee-update", r: new-rate })
    (ok true)))

;; PROPERTY MANAGEMENT
(define-public (submit-property 
    (title (string-ascii 100)) 
    (location (string-ascii 100)) 
    (total-value-sbtc uint)
    (monthly-rent-sbtc uint) 
    (min-investment-sbtc uint) 
    (funding-days uint) 
    (funding-threshold uint))
  (begin
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    (unwrap! (check-emergency-status) ERR_CONTRACT_PAUSED)
    
    (asserts! (and (> funding-days u0) (<= funding-days u90)) ERR_INVALID_INPUT)
    (asserts! (is-valid-property-value total-value-sbtc) ERR_PROPERTY_VALUE_TOO_HIGH)
    
    (asserts! (and (>= min-investment-sbtc u1000000)
                   (<= min-investment-sbtc total-value-sbtc)
                   (> monthly-rent-sbtc u0)
                   (>= funding-threshold MIN_FUNDING_THRESHOLD)
                   (<= funding-threshold MAX_FUNDING_THRESHOLD)
                   (>= (len title) u1)
                   (<= (len title) u100)
                   (>= (len location) u1)
                   (<= (len location) u100)
                   (is-realistic-rent-yield monthly-rent-sbtc total-value-sbtc))
              ERR_INVALID_INPUT)
    
    (let (
      (property-id (+ (var-get property-counter) u1))
      (funding-deadline (+ stacks-block-height (unwrap! (safe-mul funding-days BLOCKS_PER_DAY) ERR_ARITHMETIC_OVERFLOW)))
    )
      (map-set properties 
        { property-id: property-id }
        { 
          owner: tx-sender, 
          title: title, 
          location: location, 
          total-value-sbtc: total-value-sbtc, 
          total-invested-sbtc: u0, 
          monthly-rent-sbtc: monthly-rent-sbtc, 
          min-investment-sbtc: min-investment-sbtc,
          is-verified: false, 
          is-active: false, 
          created-at: stacks-block-height, 
          funding-deadline: funding-deadline, 
          funding-threshold: funding-threshold, 
          funding-status: "pending", 
          is-liquidated: false, 
          liquidation-value: u0,
          funds-released: false, 
          funds-release-date: u0,
          deadline-checked: false,
          verification-score: u0,
          verified-by-governance: false
        })
      
      (var-set property-counter property-id)
      
      (print { 
        e: "submit",
        p: property-id, 
        o: tx-sender, 
        v: total-value-sbtc 
      })
      (ok property-id))))

(define-public (verify-property (property-id uint))
  (let ((property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)))
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED)
    (asserts! (not (get is-verified property)) ERR_PROPERTY_ALREADY_EXISTS)
    
    (map-set properties 
      { property-id: property-id }
      (merge property { 
        is-verified: true, 
        is-active: true, 
        funding-status: "active" 
      }))
    
    (print { e: "verify", p: property-id })
    (ok true)))

(define-public (update-property-investment (property-id uint) (additional-investment uint))
  (let ((property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)))
    (asserts! (or (is-eq contract-caller .investment-manager-v3)
                  (is-eq contract-caller .rental-distributor-v3))
              ERR_UNAUTHORIZED_CALLER)
    (asserts! (and (> property-id u0) (> additional-investment u0)) ERR_INVALID_INPUT)
    
    (let ((new-total (unwrap! (safe-add (get total-invested-sbtc property) additional-investment) ERR_ARITHMETIC_OVERFLOW)))
      (asserts! (<= new-total (get total-value-sbtc property)) ERR_INVALID_AMOUNT)
      (map-set properties 
        { property-id: property-id } 
        (merge property { total-invested-sbtc: new-total })))
    
    (print { 
      e: "invest-update",
      p: property-id, 
      a: additional-investment 
    })
    (ok true)))

(define-public (check-funding-deadline (property-id uint))
  (let ((property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)))
    (asserts! (>= stacks-block-height (get funding-deadline property)) ERR_INVALID_INPUT)
    (asserts! (is-eq (get funding-status property) "active") ERR_PROPERTY_ALREADY_EXISTS)
    
    (let (
      (funding-pct (unwrap! (safe-div (unwrap! (safe-mul (get total-invested-sbtc property) BASIS_POINTS_SCALE) ERR_ARITHMETIC_OVERFLOW)
                                       (get total-value-sbtc property))
                             ERR_ARITHMETIC_OVERFLOW))
    )
      (if (>= funding-pct (get funding-threshold property))
        (begin 
          (map-set properties 
            { property-id: property-id } 
            (merge property { 
              funding-status: "funded",
              deadline-checked: true
            }))
          (print { e: "funded", p: property-id })
          (ok "funded"))
        (begin 
          (map-set properties 
            { property-id: property-id } 
            (merge property { 
              funding-status: "failed", 
              is-active: false,
              deadline-checked: true
            }))
          (print { e: "failed", p: property-id })
          (ok "failed"))))))

;; Release-funds-to-owner: state update before transfer, proper as-contract usage
(define-public (release-funds-to-owner (property-id uint))
  (let ((property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)))
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    (unwrap! (check-emergency-status) ERR_CONTRACT_PAUSED)
    (asserts! (or (is-admin-or-owner tx-sender) 
                  (is-eq tx-sender (get owner property))) ERR_NOT_AUTHORIZED)
    
    (asserts! (and (is-eq (get funding-status property) "funded")
                   (not (get funds-released property))
                   (> stacks-block-height (+ (get funding-deadline property) FUNDS_RELEASE_DELAY)))
              ERR_FUNDING_INCOMPLETE)
    
    ;; Just mark funds as ready for release, don't transfer
    (map-set properties 
      { property-id: property-id }
      (merge property { 
        funds-released: true, 
        funds-release-date: stacks-block-height 
      }))
    
    (print { 
      e: "release-approved",
      p: property-id, 
      a: (get total-invested-sbtc property), 
      to: (get owner property) 
    })
    (ok (get total-invested-sbtc property))))

(define-public (update-property-rent (property-id uint) (new-monthly-rent uint))
  (let ((property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)))
    (asserts! (or (is-eq tx-sender (get owner property)) 
                  (is-admin-or-owner tx-sender)) ERR_NOT_AUTHORIZED)
    (asserts! (and (> new-monthly-rent u0)
                   (is-realistic-rent-yield new-monthly-rent (get total-value-sbtc property)))
              ERR_RENT_YIELD_UNREALISTIC)
    
    (map-set properties 
      { property-id: property-id } 
      (merge property { monthly-rent-sbtc: new-monthly-rent }))
    
    (print { 
      e: "rent-update",
      p: property-id, 
      r: new-monthly-rent 
    })
    (ok true)))

;; GOVERNANCE FUNCTIONS
(define-public (create-governance-proposal
    (property-id uint) 
    (proposal-type (string-ascii 50)) 
    (description (string-ascii 300)) 
    (new-value uint) 
    (new-principal-opt (optional principal)))
  (let (
    (proposal-id (+ (var-get proposal-counter) u1))
    (voting-deadline (+ stacks-block-height VOTING_PERIOD))
    (property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND))
    (snapshot-investment (get total-invested-sbtc property))
  )
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    
    (asserts! (and (> property-id u0)
                   (> (len proposal-type) u0)
                   (<= (len proposal-type) u50)
                   (> (len description) u10)
                   (<= (len description) u300)
                   (<= new-value u1000000000))
              ERR_INVALID_INPUT)
    
    (asserts! (or (is-eq proposal-type "update-rent")
                  (is-eq proposal-type "change-owner")
                  (is-eq proposal-type "liquidate")
                  (is-eq proposal-type "update-threshold")
                  (is-eq proposal-type "extend-deadline"))
              ERR_INVALID_INPUT)
    
    (if (is-eq proposal-type "liquidate")
      (let ((min-liquidation (unwrap! (safe-div (unwrap! (safe-mul (get total-value-sbtc property) MIN_LIQUIDATION_PCT) 
                                                          ERR_ARITHMETIC_OVERFLOW) 
                                                 BASIS_POINTS_SCALE) 
                                      ERR_ARITHMETIC_OVERFLOW)))
        (asserts! (>= new-value min-liquidation) ERR_LIQUIDATION_TOO_LOW))
      true)
    
    (let (
      (validated-principal 
        (match new-principal-opt
          principal-val (if (is-standard principal-val)
                         (some principal-val)
                         none)
          none))
    )
      (asserts! (or (is-none new-principal-opt)
                    (is-some validated-principal))
                ERR_INVALID_INPUT)
      
      (map-set governance-proposals 
        { proposal-id: proposal-id }
        { 
          property-id: property-id, 
          proposal-type: proposal-type, 
          description: description, 
          proposed-by: tx-sender, 
          created-at: stacks-block-height, 
          voting-deadline: voting-deadline,
          votes-for: u0, 
          votes-against: u0, 
          total-voting-power: u0, 
          executed: false, 
          execution-block: u0, 
          new-value: new-value,
          new-principal: validated-principal,
          snapshot-total-investment: snapshot-investment
        }))
    
    (var-set proposal-counter proposal-id)
    
    (print { 
      e: "proposal",
      id: proposal-id, 
      p: property-id, 
      t: proposal-type,
      s: snapshot-investment
    })
    (ok proposal-id)))

;; Signature to match investment-manager-v3: takes 4 parameters (proposal-id, voter, vote-for, voting-power)
(define-public (record-vote 
    (proposal-id uint) 
    (voter principal) 
    (vote-for bool)
    (voting-power uint))
  (let (
    (proposal (unwrap! (get-proposal proposal-id) ERR_PROPERTY_NOT_FOUND))
  )
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    
    ;; Allow direct calls when tx-sender equals voter, OR when called from authorized contracts
    (asserts! (or (is-eq tx-sender voter)
                  (is-eq contract-caller .investment-manager-v3)
                  (is-eq contract-caller .rental-distributor-v3))
              ERR_NOT_AUTHORIZED)
    
    (asserts! (and (is-none (get-user-vote proposal-id voter))
                   (<= stacks-block-height (get voting-deadline proposal))
                   (> voting-power u0)
                   (<= voting-power BASIS_POINTS_SCALE))
              ERR_ALREADY_VOTED)
    
    (map-set proposal-votes 
      { proposal-id: proposal-id, voter: voter } 
      { vote-weight: voting-power, vote-for: vote-for })
    
    (map-set governance-proposals 
      { proposal-id: proposal-id }
      (merge proposal {
        votes-for: (if vote-for 
                     (+ (get votes-for proposal) voting-power) 
                     (get votes-for proposal)),
        votes-against: (if vote-for 
                         (get votes-against proposal) 
                         (+ (get votes-against proposal) voting-power)),
        total-voting-power: (+ (get total-voting-power proposal) voting-power) 
      }))
    
    (print { 
      e: "vote",
      id: proposal-id, 
      v: voter, 
      f: vote-for, 
      w: voting-power 
    })
    (ok true)))

(define-public (execute-proposal (proposal-id uint))
  (let (
    (proposal (unwrap! (get-proposal proposal-id) ERR_PROPERTY_NOT_FOUND))
    (property (unwrap! (get-property (get property-id proposal)) ERR_PROPERTY_NOT_FOUND))
  )
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    
    (asserts! (and (not (get executed proposal))
                   (> stacks-block-height (+ (get voting-deadline proposal) PROPOSAL_EXECUTION_DELAY))
                   (< stacks-block-height (+ (get voting-deadline proposal) PROPOSAL_EXPIRY_PERIOD))
                   (> (get votes-for proposal) (get votes-against proposal)))
              ERR_PROPOSAL_NOT_PASSED)
    
    ;; Allow execution if snapshot is 0 OR if quorum requirements are met
    (let (
      (snapshot-investment (get snapshot-total-investment proposal))
      (required-quorum (unwrap! (safe-div (unwrap! (safe-mul snapshot-investment QUORUM_PERCENTAGE) ERR_ARITHMETIC_OVERFLOW) 
                                          BASIS_POINTS_SCALE) 
                                ERR_ARITHMETIC_OVERFLOW))
      (required-participation (unwrap! (safe-div (unwrap! (safe-mul snapshot-investment MIN_PARTICIPATION_PCT) ERR_ARITHMETIC_OVERFLOW) 
                                                 BASIS_POINTS_SCALE) 
                                       ERR_ARITHMETIC_OVERFLOW))
    )
      (if (> snapshot-investment u0)
        (asserts! (and (>= (get total-voting-power proposal) required-quorum)
                       (>= (get total-voting-power proposal) required-participation))
                  ERR_INSUFFICIENT_VOTES)
        true))
    
    (let (
      (execution-result
        (if (is-eq (get proposal-type proposal) "update-rent")
          (begin
            (asserts! (and (> (get new-value proposal) u0)
                           (is-realistic-rent-yield (get new-value proposal) (get total-value-sbtc property)))
                      ERR_RENT_YIELD_UNREALISTIC)
            (map-set properties 
              { property-id: (get property-id proposal) }
              (merge property { monthly-rent-sbtc: (get new-value proposal) }))
            (ok true))
          
          (if (is-eq (get proposal-type proposal) "change-owner")
            (begin
              (let ((new-owner (unwrap! (get new-principal proposal) ERR_INVALID_INPUT)))
                (asserts! (is-standard new-owner) ERR_INVALID_INPUT)
                (map-set properties 
                  { property-id: (get property-id proposal) }
                  (merge property { owner: new-owner }))
                (ok true)))
            
            (if (is-eq (get proposal-type proposal) "liquidate")
              (begin
                (asserts! (> (get new-value proposal) u0) ERR_INVALID_AMOUNT)
                (map-set properties 
                  { property-id: (get property-id proposal) }
                  (merge property { 
                    is-liquidated: true, 
                    is-active: false, 
                    liquidation-value: (get new-value proposal) 
                  }))
                (ok true))
              
              (if (is-eq (get proposal-type proposal) "update-threshold")
                (begin
                  (asserts! (and 
                              (>= (get new-value proposal) MIN_FUNDING_THRESHOLD)
                              (<= (get new-value proposal) MAX_FUNDING_THRESHOLD))
                            ERR_INVALID_INPUT)
                  (map-set properties 
                    { property-id: (get property-id proposal) }
                    (merge property { funding-threshold: (get new-value proposal) }))
                  (ok true))
                
                (if (is-eq (get proposal-type proposal) "extend-deadline")
                  (begin
                    (asserts! (and (> (get new-value proposal) u0) (<= (get new-value proposal) u90))
                              ERR_INVALID_INPUT)
                    (let ((new-deadline (+ (get funding-deadline property) 
                                          (unwrap! (safe-mul (get new-value proposal) BLOCKS_PER_DAY) 
                                                   ERR_ARITHMETIC_OVERFLOW))))
                      (map-set properties 
                        { property-id: (get property-id proposal) }
                        (merge property { funding-deadline: new-deadline }))
                      (ok true)))
                  (ok true))))))))
      
      (unwrap! execution-result ERR_INVALID_INPUT)
      
      (map-set governance-proposals 
        { proposal-id: proposal-id }
        (merge proposal { 
          executed: true, 
          execution-block: stacks-block-height 
        }))
      
      (print { 
        e: "executed",
        id: proposal-id, 
        t: (get proposal-type proposal) 
      })
      
      (ok true))))

;; LIQUIDATION FUNCTIONS
(define-public (claim-liquidation-proceeds (property-id uint))
  (let (
    (property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND))
    (user-investment (contract-call? .data-store-v3 get-user-investment property-id tx-sender))
    (user-invested (get sbtc-invested user-investment))
    (property-totals (contract-call? .data-store-v3 get-property-investment-totals property-id))
    (total-investment (get total-sbtc-invested property-totals))
    (existing-claim (map-get? liquidation-claims { property-id: property-id, investor: tx-sender }))
  )
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    
    (asserts! (and (get is-liquidated property)
                   (> user-invested u0)
                   (is-none existing-claim))
              ERR_PROPERTY_NOT_LIQUIDATED)
    
    (let ((user-share (unwrap! (safe-div (unwrap! (safe-mul (get liquidation-value property) user-invested) 
                                                   ERR_ARITHMETIC_OVERFLOW) 
                                         total-investment) 
                               ERR_ARITHMETIC_OVERFLOW)))
      (asserts! (> user-share u0) ERR_INVALID_AMOUNT)
      
      (map-set liquidation-claims
        { property-id: property-id, investor: tx-sender }
        {
          claimed: true,
          amount-claimed: user-share,
          claim-date: stacks-block-height
        })
      
      (match (as-contract (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token transfer 
                user-share 
                (as-contract tx-sender)
                tx-sender 
                (some 0x4c69717569646174696f6e)))
        success-val
          (begin
            (print { 
              e: "liq-claim",
              p: property-id, 
              i: tx-sender, 
              a: user-share 
            })
            (ok user-share))
        error-val ERR_INVALID_AMOUNT))))

;; SECONDARY MARKET FUNCTIONS
(define-public (list-shares-for-sale 
    (property-id uint) 
    (shares-for-sale uint) 
    (price-per-share uint))
  (begin
    (asserts! (not (var-get contract-paused)) ERR_CONTRACT_PAUSED)
    (unwrap! (check-emergency-status) ERR_CONTRACT_PAUSED)
    (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND)
    (asserts! (and (> property-id u0)
                   (> shares-for-sale u0)
                   (> price-per-share u0)
                   (<= shares-for-sale u1000000000))
              ERR_INVALID_AMOUNT)
    
    (let ((user-investment (contract-call? .data-store-v3 get-user-investment property-id tx-sender)))
      (asserts! (>= (get sbtc-invested user-investment) shares-for-sale) ERR_INSUFFICIENT_SHARES))
    
    (let ((existing-listing (get-share-listing property-id tx-sender)))
      (match existing-listing
        listing (asserts! (not (get is-active listing)) ERR_SHARES_NOT_AVAILABLE)
        true))
    
    (map-set share-listings 
      { property-id: property-id, seller: tx-sender }
      { 
        shares-for-sale: shares-for-sale, 
        price-per-share: price-per-share, 
        is-active: true, 
        shares-locked: shares-for-sale, 
        listing-date: stacks-block-height,
        version: u1
      })
    
    (print { 
      e: "list",
      p: property-id, 
      s: tx-sender, 
      sh: shares-for-sale, 
      pr: price-per-share 
    })
    (ok true)))

(define-public (cancel-share-listing (property-id uint))
  (let ((listing (unwrap! (get-share-listing property-id tx-sender) ERR_LISTING_NOT_FOUND)))
    (asserts! (and (not (var-get contract-paused))
                   (get is-active listing))
              ERR_LISTING_NOT_FOUND)
    
    (map-set share-listings 
      { property-id: property-id, seller: tx-sender }
      (merge listing { 
        is-active: false, 
        shares-locked: u0 
      }))
    
    (print { e: "cancel", p: property-id, s: tx-sender })
    (ok true)))

(define-public (update-share-listing-price 
    (property-id uint) 
    (new-price-per-share uint))
  (let ((listing (unwrap! (get-share-listing property-id tx-sender) ERR_LISTING_NOT_FOUND)))
    (asserts! (and (not (var-get contract-paused))
                   (get is-active listing)
                   (> new-price-per-share u0))
              ERR_LISTING_NOT_FOUND)
    
    (map-set share-listings 
      { property-id: property-id, seller: tx-sender }
      (merge listing { 
        price-per-share: new-price-per-share,
        version: (+ (get version listing) u1)
      }))
    
    (print { 
      e: "price-update",
      p: property-id, 
      s: tx-sender, 
      pr: new-price-per-share 
    })
    (ok true)))

(define-public (update-listing-after-purchase 
    (property-id uint) 
    (seller principal) 
    (shares-purchased uint))
  (let ((listing (unwrap! (get-share-listing property-id seller) ERR_LISTING_NOT_FOUND)))
    (asserts! (or (is-eq contract-caller .investment-manager-v3)
                  (is-eq contract-caller .rental-distributor-v3))
              ERR_UNAUTHORIZED_CALLER)
    (asserts! (and (is-standard seller)
                   (> property-id u0)
                   (> shares-purchased u0)
                   (get is-active listing)
                   (>= (get shares-locked listing) shares-purchased))
              ERR_SHARES_NOT_AVAILABLE)
    
    (let (
      (remaining-shares (unwrap! (safe-sub (get shares-for-sale listing) shares-purchased) ERR_ARITHMETIC_OVERFLOW))
      (remaining-locked (unwrap! (safe-sub (get shares-locked listing) shares-purchased) ERR_ARITHMETIC_OVERFLOW))
    )
      (if (is-eq remaining-shares u0)
        (map-set share-listings 
          { property-id: property-id, seller: seller }
          (merge listing { 
            shares-for-sale: u0, 
            is-active: false, 
            shares-locked: u0 
          }))
        (map-set share-listings 
          { property-id: property-id, seller: seller }
          (merge listing { 
            shares-for-sale: remaining-shares, 
            shares-locked: remaining-locked 
          }))))
    
    (print { 
      e: "list-update",
      p: property-id, 
      s: seller, 
      sh: shares-purchased 
    })
    (ok true)))

;; Admin-cancel-listing: make listing optional, create if doesn't exist
(define-public (admin-cancel-listing 
    (property-id uint) 
    (seller principal))
  (let (
    (property (unwrap! (get-property property-id) ERR_PROPERTY_NOT_FOUND))
  )
    (asserts! (is-admin-or-owner tx-sender) ERR_NOT_AUTHORIZED)
    (asserts! (and (is-standard seller)
                   (> property-id u0))
              ERR_PROPERTY_NOT_FOUND)
    
    ;; Get listing or create default cancelled listing
    (let ((listing (default-to 
                     { shares-for-sale: u0, price-per-share: u0, is-active: false, 
                       shares-locked: u0, listing-date: u0, version: u0 }
                     (get-share-listing property-id seller))))
      
      (map-set share-listings 
        { property-id: property-id, seller: seller }
        (merge listing { 
          is-active: false, 
          shares-locked: u0 
        }))
      
      (print { 
        e: "admin-cancel",
        p: property-id, 
        s: seller 
      })
      (ok true))))

Functions (48)

FunctionAccessArgs
safe-addprivatea: uint, b: uint
safe-subprivatea: uint, b: uint
safe-mulprivatea: uint, b: uint
safe-divprivatea: uint, b: uint
get-property-investment-totals-publicread-onlyproperty-id: uint
get-user-investment-publicread-onlyproperty-id: uint, investor: principal
is-valid-property-valueprivatevalue: uint
calculate-target-monthly-rentprivateproperty-value: uint
is-realistic-rent-yieldprivatemonthly-rent: uint, property-value: uint
is-authorized-contractprivatecaller: principal
is-governance-adminprivatecaller: principal
is-admin-or-ownerprivatecaller: principal
check-emergency-statusprivate
is-contract-pausedread-only
get-propertyread-onlyproperty-id: uint
get-property-countread-only
get-platform-fee-rateread-only
is-contract-ownerread-onlyuser: principal
is-whitelistedread-onlyinvestor: principal
is-blacklistedread-onlyinvestor: principal
get-proposalread-onlyproposal-id: uint
get-user-voteread-onlyproposal-id: uint, voter: principal
get-share-listingread-onlyproperty-id: uint, seller: principal
get-executed-governance-actionread-onlyaction-id: uint
get-funding-inforead-onlyproperty-id: uint
can-execute-proposalread-onlyproposal-id: uint
get-available-shares-for-listingread-onlyproperty-id: uint, seller: principal
pause-contractpublic
unpause-contractpublic
execute-governance-actionpublicaction-id: uint
whitelist-investorpublicinvestor: principal
blacklist-investorpublicinvestor: principal
update-platform-fee-ratepublicnew-rate: uint
submit-propertypublictitle: (string-ascii 100
verify-propertypublicproperty-id: uint
update-property-investmentpublicproperty-id: uint, additional-investment: uint
check-funding-deadlinepublicproperty-id: uint
release-funds-to-ownerpublicproperty-id: uint
update-property-rentpublicproperty-id: uint, new-monthly-rent: uint
create-governance-proposalpublicproperty-id: uint, proposal-type: (string-ascii 50
record-votepublicproposal-id: uint, voter: principal, vote-for: bool, voting-power: uint
execute-proposalpublicproposal-id: uint
claim-liquidation-proceedspublicproperty-id: uint
list-shares-for-salepublicproperty-id: uint, shares-for-sale: uint, price-per-share: uint
cancel-share-listingpublicproperty-id: uint
update-share-listing-pricepublicproperty-id: uint, new-price-per-share: uint
update-listing-after-purchasepublicproperty-id: uint, seller: principal, shares-purchased: uint
admin-cancel-listingpublicproperty-id: uint, seller: principal