Source Code

;; Title: BME030 Reputation Token
;; Synopsis:
;; Wraps reputation scheme within a non-transferable soulbound semi fungible token (see sip-013).
;; Description:
;; The reputation token is a SIP-013 compliant token that is controlled by active DAO extensions.
;; It facilitates hierarchical reputation and rewards based on engagements across a number of
;; BigMarket DAO features and use cases. 

(impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)
(impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait.sip013-transfer-many-trait)

(define-constant err-unauthorised (err u30001))
(define-constant err-already-minted (err u30002))
(define-constant err-soulbound (err u30003))
(define-constant err-insufficient-balance (err u30004))
(define-constant err-zero-amount (err u30005))
(define-constant err-claims-old-epoch (err u30006))
(define-constant err-claims-zero-rep (err u30007))
(define-constant err-claims-zero-total (err u30008))
(define-constant err-invalid-tier (err u30009))

(define-constant max-tier u20)

(define-fungible-token bigr-token)
(define-non-fungible-token bigr-id { token-id: uint, owner: principal })

(define-map balances { token-id: uint, owner: principal } uint)
(define-map supplies uint uint)
(define-map last-claimed-epoch { who: principal } uint)
(define-map tier-weights uint uint)

(define-data-var reward-per-epoch uint u1000000000) ;; 1000 BIG (in micro units)
(define-data-var overall-supply uint u0)
(define-data-var token-name (string-ascii 32) "BigMarket Reputation Token")
(define-data-var token-symbol (string-ascii 10) "BIGR")
(define-data-var launch-height uint u0)

;; ------------------------
;; DAO Control Check
;; ------------------------
(define-public (is-dao-or-extension)
	(ok (asserts! (or (is-eq tx-sender .bigmarket-dao) (contract-call? .bigmarket-dao is-extension contract-caller)) err-unauthorised))
)

(define-read-only (get-epoch)
	 (/ burn-block-height u4000)
)

(define-read-only (get-last-claimed-epoch (user principal))
  (default-to u0 (map-get? last-claimed-epoch { who: user }))
)

;; ------------------------
;; Trait Implementations
;; ------------------------
(define-read-only (get-balance (token-id uint) (who principal))
  (ok (default-to u0 (map-get? balances { token-id: token-id, owner: who })))
)

(define-public (set-launch-height)
  (begin
    (try! (is-dao-or-extension))
    (asserts! (is-eq (var-get launch-height) u0) err-unauthorised)
    (var-set launch-height burn-block-height)
    (ok (var-get launch-height))
  )
)

(define-read-only (get-symbol)
	(ok (var-get token-symbol))
)

(define-read-only (get-name)
	(ok (var-get token-name))
)

(define-read-only (get-overall-balance (who principal))
  (ok (ft-get-balance bigr-token who))
)

(define-read-only (get-total-supply (token-id uint))
  (ok (default-to u0 (map-get? supplies token-id)))
)

(define-read-only (get-overall-supply)
  (ok (var-get overall-supply))
)

(define-read-only (get-decimals (token-id uint)) (ok u0))

(define-read-only (get-token-uri (token-id uint))
  (ok none)
)

(define-public (set-reward-per-epoch (new-reward uint))
  (begin
    (try! (is-dao-or-extension))
    (var-set reward-per-epoch new-reward)
    (print { event: "set-reward-per-epoch", new-reward: new-reward })
    (ok true)
  )
)
(define-public (set-tier-weight (token-id uint) (weight uint))
  (begin
    (try! (is-dao-or-extension))
    (map-set tier-weights token-id weight)
    (print { event: "set-tier-weight", token-id: token-id, weight: weight })
    (ok true)
  )
)

;; ------------------------
;; Mint / Burn
;; ------------------------
(define-public (mint (recipient principal) (token-id uint) (amount uint))
  (let (
    (base-amount
      (if (not is-in-mainnet)
          (* amount u2) ;; testnet: 2x
          (if (< burn-block-height (+ (var-get launch-height) u12000))
              (/ (* amount u3) u2) ;; 1.5x on early mainnet
              amount))) ;; no early adopter bonus
  )
    (begin
      (try! (is-dao-or-extension))
      (asserts! (> base-amount u0) err-zero-amount)
      (asserts! (and (> token-id u0) (<= token-id max-tier)) err-invalid-tier)
      (try! (ft-mint? bigr-token base-amount recipient))
      (try! (tag-nft { token-id: token-id, owner: recipient }))
      (map-set balances { token-id: token-id, owner: recipient }
        (+ base-amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
      (map-set supplies token-id (+ base-amount (default-to u0 (map-get? supplies token-id))))
      (var-set overall-supply (+ (var-get overall-supply) base-amount))
      (print { event: "sft_mint", token-id: token-id, amount: base-amount, recipient: recipient })
      (ok true)
    )
  )
)

(define-public (burn (owner principal) (token-id uint) (amount uint))
  (begin
    (try! (is-dao-or-extension))
    (let ((current (default-to u0 (map-get? balances { token-id: token-id, owner: owner }))))
      (asserts! (>= current amount) err-insufficient-balance)
      (try! (ft-burn? bigr-token amount owner))
      (map-set balances { token-id: token-id, owner: owner } (- current amount))
      (map-set supplies token-id (- (unwrap-panic (get-total-supply token-id)) amount))
      (var-set overall-supply (- (var-get overall-supply) amount))
      (try! (nft-burn? bigr-id { token-id: token-id, owner: owner } owner))
      (print { event: "sft_burn", token-id: token-id, amount: amount, sender: owner })
      (ok true)
    )
  )
)

;; ------------------------
;; Transfer (DAO-only)
;; ------------------------
(define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
  (begin
    (try! (is-dao-or-extension))
    (asserts! (> amount u0) err-zero-amount)
    (let ((sender-balance (default-to u0 (map-get? balances { token-id: token-id, owner: sender }))))
      (asserts! (>= sender-balance amount) err-insufficient-balance)
      (try! (ft-transfer? bigr-token amount sender recipient))
      (try! (tag-nft { token-id: token-id, owner: sender }))
      (try! (tag-nft { token-id: token-id, owner: recipient }))
      (map-set balances { token-id: token-id, owner: sender } (- sender-balance amount))
      (map-set balances { token-id: token-id, owner: recipient }
        (+ amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
      (print { event: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient })
      (ok true)
    )
  )
)

(define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
  (begin
    (try! (transfer token-id amount sender recipient))
    (print memo)
    (ok true)
  )
)

(define-public (transfer-many (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal })))
  (fold transfer-many-iter transfers (ok true))
)

(define-public (transfer-many-memo (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) })))
  (fold transfer-many-memo-iter transfers (ok true))
)

;; -------------------------
;; Claims for big from bigr
;; individual and batch supported..
;; -------------------------

(define-public (claim-big-reward)
  (claim-big-reward-for-user tx-sender)
)

(define-public (claim-big-reward-batch (users (list 100 principal)))
  (fold claim-big-reward-batch-iter users (ok u0))
)

(define-private (claim-big-reward-batch-iter (user principal) (acc (response uint uint)))
  (let (
        (a (unwrap! acc err-claims-zero-total))
        (b (unwrap! (claim-big-reward-for-user user) err-claims-zero-total))
      )
    (ok (+ a b))
  )
)

(define-private (claim-big-reward-for-user (user principal)) ;; returns share or u0
  (let (
        (epoch (/ burn-block-height u4000))
        (last (default-to u0 (map-get? last-claimed-epoch { who: user })))
      )
    (if (< last epoch)
      (let (
            (rep (unwrap! (get-weighted-rep user) err-claims-zero-rep))
            (total (unwrap! (get-weighted-supply) err-claims-zero-total))
          )
        (if (and (> rep u0) (> total u0))
          (let ((share (/ (* rep (var-get reward-per-epoch)) total)))
            (map-set last-claimed-epoch { who: user } epoch)
            (try! (contract-call? .bme006-0-treasury sip010-transfer share user none .bme000-0-governance-token))
            (print { event: "big-claim", user: user, epoch: epoch, amount: share, reward-per-epoch: (var-get reward-per-epoch) })
            (ok share)
          )
          (ok u0)
        )
      )
      (ok u0)
    )
  )
)

;; ------------------------
;; Helpers
;; ------------------------
(define-private (tag-nft (nft-token-id { token-id: uint, owner: principal }))
  (begin
    (if (is-some (nft-get-owner? bigr-id nft-token-id))
      (try! (nft-burn? bigr-id nft-token-id (get owner nft-token-id)))
      true)
    (nft-mint? bigr-id nft-token-id (get owner nft-token-id))
  )
)

(define-private (transfer-many-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal }) (prev (response bool uint)))
  (match prev ok-prev (transfer (get token-id item) (get amount item) (get sender item) (get recipient item)) err-prev prev)
)

(define-private (transfer-many-memo-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) }) (prev (response bool uint)))
  (match prev ok-prev (transfer-memo (get token-id item) (get amount item) (get sender item) (get recipient item) (get memo item)) err-prev prev)
)

;; dynamic weighted totals for user
(define-read-only (get-weighted-rep (user principal))
  (let (
        (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10))
        (result (fold add-weighted-rep-for-user tiers (tuple (acc u0) (user user))))
  )
    (ok (get acc result))
  )
)

(define-private (add-weighted-rep-for-user (token-id uint) (state (tuple (acc uint) (user principal))))
  (let (
    (acc (get acc state))
    (user (get user state))
    (bal-at-tier (default-to u0 (map-get? balances {token-id: token-id, owner: user})))
    (weight-at-tier (default-to u1 (map-get? tier-weights token-id)))
  )
    (tuple (acc (+ acc (* bal-at-tier weight-at-tier))) (user user))
  )
)

;; dynamic weighted totals for overall supply pool
(define-read-only (get-weighted-supply)
  (let (
    (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10))
    (result (fold add-weighted-supply-for-tier tiers u0))
  )
    (ok result)
  )
)

(define-private (add-weighted-supply-for-tier (token-id uint) (acc uint))
  (let (
    (tier-supply (default-to u0 (map-get? supplies token-id)))
    (weight (default-to u1 (map-get? tier-weights token-id)))
  )
    (+ acc (* tier-supply weight))
  )
)

Functions (29)

FunctionAccessArgs
is-dao-or-extensionpublic
get-epochread-only
get-last-claimed-epochread-onlyuser: principal
get-balanceread-onlytoken-id: uint, who: principal
set-launch-heightpublic
get-symbolread-only
get-nameread-only
get-overall-balanceread-onlywho: principal
get-total-supplyread-onlytoken-id: uint
get-overall-supplyread-only
get-decimalsread-onlytoken-id: uint
get-token-uriread-onlytoken-id: uint
set-reward-per-epochpublicnew-reward: uint
set-tier-weightpublictoken-id: uint, weight: uint
mintpublicrecipient: principal, token-id: uint, amount: uint
burnpublicowner: principal, token-id: uint, amount: uint
transferpublictoken-id: uint, amount: uint, sender: principal, recipient: principal
transfer-memopublictoken-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34
transfer-manypublictransfers: (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal }
claim-big-rewardpublic
claim-big-reward-batchpublicusers: (list 100 principal
claim-big-reward-batch-iterprivateuser: principal, acc: (response uint uint
claim-big-reward-for-userprivateuser: principal
tag-nftprivatenft-token-id: { token-id: uint, owner: principal }
transfer-many-iterprivateitem: { token-id: uint, amount: uint, sender: principal, recipient: principal }, prev: (response bool uint
get-weighted-repread-onlyuser: principal
add-weighted-rep-for-userprivatetoken-id: uint, state: (tuple (acc uint, user: principal
get-weighted-supplyread-only
add-weighted-supply-for-tierprivatetoken-id: uint, acc: uint