Source Code

;; StackSusu Referral v6
;; Enhanced referral program with multi-level rewards and time-limited bonuses

(define-constant CONTRACT-OWNER tx-sender)

;; Error constants
(define-constant ERR-NOT-AUTHORIZED (err u3000))
(define-constant ERR-ALREADY-REFERRED (err u3001))
(define-constant ERR-SELF-REFERRAL (err u3002))
(define-constant ERR-REFERRER-NOT-FOUND (err u3003))
(define-constant ERR-TRANSFER-FAILED (err u3004))
(define-constant ERR-PAUSED (err u3005))
(define-constant ERR-INVALID-REFERRER (err u3006))
(define-constant ERR-EXPIRED-CODE (err u3007))
(define-constant ERR-INVALID-TIER (err u3008))
(define-constant ERR-MAX-DEPTH-REACHED (err u3009))

;; Multi-level referral settings (NEW in v6)
(define-constant MAX-REFERRAL-DEPTH u3)  ;; Up to 3 levels deep
(define-constant LEVEL-1-PERCENT u50)    ;; 50% to direct referrer
(define-constant LEVEL-2-PERCENT u30)    ;; 30% to L2 referrer
(define-constant LEVEL-3-PERCENT u20)    ;; 20% to L3 referrer

;; Referral relationships
(define-map referrals
  principal  ;; referred member
  {
    referrer: principal,
    referred-at: uint,
    circles-joined: uint,
    total-volume: uint,
    level-2-referrer: (optional principal),  ;; NEW: Track chain
    level-3-referrer: (optional principal)   ;; NEW: Track chain
  }
)

;; Referrer statistics
(define-map referrer-stats
  principal
  {
    total-referrals: uint,
    active-referrals: uint,
    total-earned: uint,
    pending-rewards: uint,
    last-payout: uint,
    level-2-referrals: uint,   ;; NEW: Indirect referrals
    level-3-referrals: uint    ;; NEW: 3rd level referrals
  }
)

;; Pending rewards per referrer
(define-map pending-rewards principal uint)

;; Referral codes (NEW in v6)
(define-map referral-codes
  (string-ascii 20)  ;; code
  {
    owner: principal,
    created-at: uint,
    expires-at: uint,
    uses: uint,
    max-uses: uint,
    bonus-percent: uint  ;; Extra bonus for using this code
  }
)

(define-map member-referral-code
  principal
  (string-ascii 20)
)

;; Time-limited bonus campaigns (NEW in v6)
(define-map bonus-campaigns
  uint  ;; campaign-id
  {
    name: (string-ascii 50),
    multiplier: uint,  ;; 100 = 1x, 150 = 1.5x, 200 = 2x
    start-block: uint,
    end-block: uint,
    active: bool
  }
)

(define-data-var campaign-counter uint u0)
(define-data-var active-campaign-id uint u0)

;; Referral tiers
(define-constant TIER-1-THRESHOLD u5)
(define-constant TIER-2-THRESHOLD u20)
(define-constant TIER-3-THRESHOLD u50)
(define-constant TIER-4-THRESHOLD u100)

(define-constant TIER-0-MULTIPLIER u100)
(define-constant TIER-1-MULTIPLIER u125)
(define-constant TIER-2-MULTIPLIER u150)
(define-constant TIER-3-MULTIPLIER u175)
(define-constant TIER-4-MULTIPLIER u200)

;; Authorized contracts
(define-map authorized-callers principal bool)


;; ============================================
;; Authorization
;; ============================================

(define-read-only (is-authorized (caller principal))
  (or 
    (is-eq caller CONTRACT-OWNER)
    (default-to false (map-get? authorized-callers caller))
  )
)

(define-public (authorize-caller (caller principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (map-set authorized-callers caller true))
  )
)

(define-public (revoke-caller (caller principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (map-delete authorized-callers caller))
  )
)


;; ============================================
;; Referral Code Management (NEW in v6)
;; ============================================

(define-public (create-referral-code 
    (code (string-ascii 20)) 
    (max-uses uint) 
    (valid-blocks uint)
    (bonus-percent uint))
  (let
    (
      (owner tx-sender)
    )
    (asserts! (is-none (map-get? referral-codes code)) ERR-ALREADY-REFERRED)
    (asserts! (<= bonus-percent u50) ERR-INVALID-TIER)  ;; Max 50% bonus
    
    (map-set referral-codes code {
      owner: owner,
      created-at: block-height,
      expires-at: (+ block-height valid-blocks),
      uses: u0,
      max-uses: max-uses,
      bonus-percent: bonus-percent
    })
    
    (map-set member-referral-code owner code)
    
    (ok true)
  )
)

(define-read-only (validate-referral-code (code (string-ascii 20)))
  (match (map-get? referral-codes code)
    code-data
      (and 
        (< block-height (get expires-at code-data))
        (< (get uses code-data) (get max-uses code-data))
      )
    false
  )
)


;; ============================================
;; Referral Registration
;; ============================================

(define-public (register-referral (referrer principal))
  (let
    (
      (new-member tx-sender)
      (existing-referral (map-get? referrals new-member))
      (referrer-referral (map-get? referrals referrer))
      (l2-referrer (match referrer-referral ref (some (get referrer ref)) none))
      (l3-referrer (match referrer-referral ref (get level-2-referrer ref) none))
      (referrer-data (default-to {
        total-referrals: u0,
        active-referrals: u0,
        total-earned: u0,
        pending-rewards: u0,
        last-payout: u0,
        level-2-referrals: u0,
        level-3-referrals: u0
      } (map-get? referrer-stats referrer)))
    )
    (asserts! (not (contract-call? .stacksusu-admin-v6 is-paused)) ERR-PAUSED)
    (asserts! (is-none existing-referral) ERR-ALREADY-REFERRED)
    (asserts! (not (is-eq new-member referrer)) ERR-SELF-REFERRAL)
    
    ;; Record referral with chain
    (map-set referrals new-member {
      referrer: referrer,
      referred-at: block-height,
      circles-joined: u0,
      total-volume: u0,
      level-2-referrer: l2-referrer,
      level-3-referrer: l3-referrer
    })
    
    ;; Update L1 referrer stats
    (map-set referrer-stats referrer
      (merge referrer-data {
        total-referrals: (+ (get total-referrals referrer-data) u1),
        active-referrals: (+ (get active-referrals referrer-data) u1)
      })
    )
    
    ;; Update L2 referrer stats if exists
    (match l2-referrer
      l2
        (let
          (
            (l2-data (default-to {
              total-referrals: u0,
              active-referrals: u0,
              total-earned: u0,
              pending-rewards: u0,
              last-payout: u0,
              level-2-referrals: u0,
              level-3-referrals: u0
            } (map-get? referrer-stats l2)))
          )
          (map-set referrer-stats l2
            (merge l2-data { level-2-referrals: (+ (get level-2-referrals l2-data) u1) }))
        )
      true
    )
    
    ;; Update L3 referrer stats if exists
    (match l3-referrer
      l3
        (let
          (
            (l3-data (default-to {
              total-referrals: u0,
              active-referrals: u0,
              total-earned: u0,
              pending-rewards: u0,
              last-payout: u0,
              level-2-referrals: u0,
              level-3-referrals: u0
            } (map-get? referrer-stats l3)))
          )
          (map-set referrer-stats l3
            (merge l3-data { level-3-referrals: (+ (get level-3-referrals l3-data) u1) }))
        )
      true
    )
    
    ;; Record in reputation
    (try! (contract-call? .stacksusu-reputation-v6 record-referral referrer))
    
    (ok true)
  )
)

(define-public (register-with-code (code (string-ascii 20)))
  (let
    (
      (code-data (unwrap! (map-get? referral-codes code) ERR-REFERRER-NOT-FOUND))
      (referrer (get owner code-data))
    )
    (asserts! (validate-referral-code code) ERR-EXPIRED-CODE)
    
    ;; Update code usage
    (map-set referral-codes code
      (merge code-data { uses: (+ (get uses code-data) u1) }))
    
    ;; Register the referral
    (register-referral referrer)
  )
)


;; ============================================
;; Reward Distribution (Multi-Level)
;; ============================================

(define-public (record-activity (member principal) (amount uint))
  (let
    (
      (referral-data (map-get? referrals member))
      (base-reward-bps (contract-call? .stacksusu-admin-v6 get-referral-fee-bps))
      (campaign-multiplier (get-active-campaign-multiplier))
      (base-reward (/ (* amount base-reward-bps campaign-multiplier) u1000000))
    )
    (asserts! (is-authorized contract-caller) ERR-NOT-AUTHORIZED)
    
    (match referral-data
      ref-data
        (begin
          ;; Update referred member's stats
          (map-set referrals member
            (merge ref-data {
              circles-joined: (+ (get circles-joined ref-data) u1),
              total-volume: (+ (get total-volume ref-data) amount)
            })
          )
          
          ;; Distribute rewards across levels
          (let
            (
              (l1-reward (/ (* base-reward LEVEL-1-PERCENT) u100))
              (l2-reward (/ (* base-reward LEVEL-2-PERCENT) u100))
              (l3-reward (/ (* base-reward LEVEL-3-PERCENT) u100))
            )
            ;; L1 reward
            (credit-referrer (get referrer ref-data) l1-reward)
            
            ;; L2 reward
            (match (get level-2-referrer ref-data)
              l2 (credit-referrer l2 l2-reward)
              true
            )
            
            ;; L3 reward
            (match (get level-3-referrer ref-data)
              l3 (credit-referrer l3 l3-reward)
              true
            )
          )
          
          (ok base-reward)
        )
      (ok u0)
    )
  )
)

(define-private (credit-referrer (referrer principal) (amount uint))
  (let
    (
      (current-pending (default-to u0 (map-get? pending-rewards referrer)))
      (tier-multiplier (get-referrer-tier-multiplier referrer))
      (final-amount (/ (* amount tier-multiplier) u100))
    )
    (map-set pending-rewards referrer (+ current-pending final-amount))
    true
  )
)


;; ============================================
;; Bonus Campaigns (NEW in v6)
;; ============================================

(define-public (create-campaign 
    (name (string-ascii 50))
    (multiplier uint)
    (duration-blocks uint))
  (let
    (
      (campaign-id (+ (var-get campaign-counter) u1))
    )
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    
    (map-set bonus-campaigns campaign-id {
      name: name,
      multiplier: multiplier,
      start-block: block-height,
      end-block: (+ block-height duration-blocks),
      active: true
    })
    
    (var-set campaign-counter campaign-id)
    (var-set active-campaign-id campaign-id)
    
    (ok campaign-id)
  )
)

(define-public (end-campaign (campaign-id uint))
  (let
    (
      (campaign (unwrap! (map-get? bonus-campaigns campaign-id) ERR-NOT-AUTHORIZED))
    )
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    
    (map-set bonus-campaigns campaign-id (merge campaign { active: false }))
    
    (if (is-eq campaign-id (var-get active-campaign-id))
      (var-set active-campaign-id u0)
      true
    )
    
    (ok true)
  )
)

(define-read-only (get-active-campaign-multiplier)
  (let
    (
      (campaign-id (var-get active-campaign-id))
    )
    (if (is-eq campaign-id u0)
      u100  ;; No campaign = 1x
      (match (map-get? bonus-campaigns campaign-id)
        campaign
          (if (and (get active campaign) (< block-height (get end-block campaign)))
            (get multiplier campaign)
            u100)
        u100
      )
    )
  )
)


;; ============================================
;; Claim Rewards
;; ============================================

(define-public (claim-rewards)
  (let
    (
      (claimer tx-sender)
      (pending (default-to u0 (map-get? pending-rewards claimer)))
      (stats (default-to {
        total-referrals: u0,
        active-referrals: u0,
        total-earned: u0,
        pending-rewards: u0,
        last-payout: u0,
        level-2-referrals: u0,
        level-3-referrals: u0
      } (map-get? referrer-stats claimer)))
    )
    (asserts! (> pending u0) ERR-NOT-AUTHORIZED)
    
    ;; Transfer rewards
    (match (as-contract (stx-transfer? pending tx-sender claimer))
      success
        (begin
          (map-set pending-rewards claimer u0)
          (map-set referrer-stats claimer
            (merge stats {
              total-earned: (+ (get total-earned stats) pending),
              pending-rewards: u0,
              last-payout: block-height
            })
          )
          (try! (contract-call? .stacksusu-admin-v6 record-referral-payout pending))
          (ok pending)
        )
      error ERR-TRANSFER-FAILED
    )
  )
)


;; ============================================
;; Read-Only Functions
;; ============================================

(define-read-only (get-referrer (member principal))
  (match (map-get? referrals member)
    ref-data (some (get referrer ref-data))
    none
  )
)

(define-read-only (has-referrer (member principal))
  (is-some (map-get? referrals member))
)

(define-read-only (get-referral-chain (member principal))
  (match (map-get? referrals member)
    ref-data
      (ok (list 
        (get referrer ref-data)
        (default-to (get referrer ref-data) (get level-2-referrer ref-data))
        (default-to (get referrer ref-data) (get level-3-referrer ref-data))
      ))
    (ok (list))
  )
)

(define-read-only (get-referrer-stats (referrer principal))
  (map-get? referrer-stats referrer)
)

(define-read-only (get-pending-rewards (referrer principal))
  (default-to u0 (map-get? pending-rewards referrer))
)

(define-read-only (get-referrer-tier-multiplier (referrer principal))
  (let
    (
      (stats (default-to {
        total-referrals: u0,
        active-referrals: u0,
        total-earned: u0,
        pending-rewards: u0,
        last-payout: u0,
        level-2-referrals: u0,
        level-3-referrals: u0
      } (map-get? referrer-stats referrer)))
      (total (get total-referrals stats))
    )
    (if (>= total TIER-4-THRESHOLD)
      TIER-4-MULTIPLIER
      (if (>= total TIER-3-THRESHOLD)
        TIER-3-MULTIPLIER
        (if (>= total TIER-2-THRESHOLD)
          TIER-2-MULTIPLIER
          (if (>= total TIER-1-THRESHOLD)
            TIER-1-MULTIPLIER
            TIER-0-MULTIPLIER))))
  )
)

(define-read-only (get-referral-code-info (code (string-ascii 20)))
  (map-get? referral-codes code)
)

(define-read-only (get-member-code (member principal))
  (map-get? member-referral-code member)
)

(define-read-only (get-campaign (campaign-id uint))
  (map-get? bonus-campaigns campaign-id)
)

(define-read-only (get-active-campaign)
  (let
    (
      (campaign-id (var-get active-campaign-id))
    )
    (if (> campaign-id u0)
      (map-get? bonus-campaigns campaign-id)
      none
    )
  )
)

Functions (23)

FunctionAccessArgs
is-authorizedread-onlycaller: principal
authorize-callerpubliccaller: principal
revoke-callerpubliccaller: principal
create-referral-codepubliccode: (string-ascii 20
validate-referral-coderead-onlycode: (string-ascii 20
register-referralpublicreferrer: principal
register-with-codepubliccode: (string-ascii 20
record-activitypublicmember: principal, amount: uint
credit-referrerprivatereferrer: principal, amount: uint
create-campaignpublicname: (string-ascii 50
end-campaignpubliccampaign-id: uint
get-active-campaign-multiplierread-only
claim-rewardspublic
get-referrerread-onlymember: principal
has-referrerread-onlymember: principal
get-referral-chainread-onlymember: principal
get-referrer-statsread-onlyreferrer: principal
get-pending-rewardsread-onlyreferrer: principal
get-referrer-tier-multiplierread-onlyreferrer: principal
get-referral-code-inforead-onlycode: (string-ascii 20
get-member-coderead-onlymember: principal
get-campaignread-onlycampaign-id: uint
get-active-campaignread-only