Branch data Line data Source code
1 : : ;; Title: BME030 Reputation Token
2 : : ;; Synopsis:
3 : : ;; Wraps reputation scheme within a non-transferable soulbound semi fungible token (see sip-013).
4 : : ;; Description:
5 : : ;; The reputation token is a SIP-013 compliant token that is controlled by active DAO extensions.
6 : : ;; It facilitates hierarchical reputation and rewards based on engagements across a number of
7 : : ;; BigMarket DAO features and use cases.
8 : :
9 : : (impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)
10 : : (impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait.sip013-transfer-many-trait)
11 : : (impl-trait 'SP3JP0N1ZXGASRJ0F7QAHWFPGTVK9T2XNXDB908Z.extension-trait.extension-trait)
12 : :
13 : : (define-constant err-unauthorised (err u30001))
14 : : (define-constant err-already-minted (err u30002))
15 : : (define-constant err-soulbound (err u30003))
16 : : (define-constant err-insufficient-balance (err u30004))
17 : : (define-constant err-zero-amount (err u30005))
18 : : (define-constant err-claims-old-epoch (err u30006))
19 : : (define-constant err-claims-zero-rep (err u30007))
20 : : (define-constant err-claims-zero-total (err u30008))
21 : : (define-constant err-invalid-tier (err u30009))
22 : : (define-constant err-too-high (err u30010))
23 : :
24 : : (define-constant max-tier u20)
25 : : (define-constant epoch-duration u1000)
26 : : (define-constant SCALE u1000000)
27 : :
28 : : (define-fungible-token bigr-token)
29 : : (define-non-fungible-token bigr-id { token-id: uint, owner: principal })
30 : :
31 : : (define-map balances { token-id: uint, owner: principal } uint)
32 : : (define-map supplies uint uint)
33 : : (define-map last-claimed-epoch { who: principal } uint)
34 : : (define-map tier-weights uint uint)
35 : : (define-map join-epoch { who: principal } uint)
36 : : (define-map minted-in-epoch { epoch: uint } uint)
37 : : (define-map minted-in-epoch-by { epoch: uint, who: principal } uint)
38 : :
39 : : (define-data-var reward-per-epoch uint u10000000000) ;; 10,000 BIG per epoch (in micro units)
40 : : (define-data-var overall-supply uint u0)
41 : : (define-data-var token-name (string-ascii 32) "BigMarket Reputation Token")
42 : : (define-data-var token-symbol (string-ascii 10) "BIGR")
43 : : (define-data-var launch-height uint u0)
44 : :
45 : : ;; ------------------------
46 : : ;; DAO Control Check
47 : : ;; ------------------------
48 : 421 : (define-public (is-dao-or-extension)
49 [ + ][ + + ]: 9586 : (ok (asserts! (or (is-eq tx-sender .bigmarket-dao) (contract-call? .bigmarket-dao is-extension contract-caller)) err-unauthorised))
50 : : )
51 : :
52 : 10 : (define-read-only (get-epoch)
53 : 68 : (/ burn-block-height epoch-duration)
54 : : )
55 : :
56 : 4 : (define-read-only (get-last-claimed-epoch (user principal))
57 : 34 : (default-to u0 (map-get? last-claimed-epoch { who: user }))
58 : : )
59 : :
60 : 10 : (define-read-only (get-latest-claimable-epoch)
61 : 52 : (let ((cur (get-epoch)))
62 [ + + ]: 52 : (if (> cur u0) (- cur u1) u0) ;; floor and subtract 1, but never negative
63 : : )
64 : : )
65 : :
66 : : ;; ------------------------
67 : : ;; Trait Implementations
68 : : ;; ------------------------
69 : 4 : (define-read-only (get-balance (token-id uint) (who principal))
70 : 17 : (ok (default-to u0 (map-get? balances { token-id: token-id, owner: who })))
71 : : )
72 : :
73 : 421 : (define-public (set-launch-height)
74 : 422 : (begin
75 : 422 : (try! (is-dao-or-extension))
76 [ - ]: 421 : (asserts! (is-eq (var-get launch-height) u0) err-unauthorised)
77 : 421 : (var-set launch-height burn-block-height)
78 : 421 : (ok (var-get launch-height))
79 : : )
80 : : )
81 : :
82 : 0 : (define-read-only (get-symbol)
83 : 0 : (ok (var-get token-symbol))
84 : : )
85 : :
86 : 0 : (define-read-only (get-name)
87 : 0 : (ok (var-get token-name))
88 : : )
89 : :
90 : 7 : (define-read-only (get-overall-balance (who principal))
91 : 16 : (ok (ft-get-balance bigr-token who))
92 : : )
93 : :
94 : 4 : (define-read-only (get-total-supply (token-id uint))
95 : 6 : (ok (default-to u0 (map-get? supplies token-id)))
96 : : )
97 : :
98 : 1 : (define-read-only (get-overall-supply)
99 : 1 : (ok (var-get overall-supply))
100 : : )
101 : :
102 : 2 : (define-read-only (get-decimals (token-id uint)) (ok u0))
103 : :
104 : 1 : (define-read-only (get-token-uri (token-id uint))
105 : 1 : (ok none)
106 : : )
107 : :
108 : 2 : (define-public (set-reward-per-epoch (new-reward uint))
109 : 2 : (begin
110 : 2 : (try! (is-dao-or-extension))
111 [ - ]: 1 : (asserts! (<= new-reward u100000000000) err-too-high) ;; cap at 100k BIG
112 : 1 : (var-set reward-per-epoch new-reward)
113 : 1 : (print { event: "set-reward-per-epoch", new-reward: new-reward })
114 : 1 : (ok true)
115 : : )
116 : : )
117 : 421 : (define-public (set-tier-weight (token-id uint) (weight uint))
118 : 8443 : (begin
119 : 8443 : (try! (is-dao-or-extension))
120 : 8442 : (map-set tier-weights token-id weight)
121 : 8442 : (print { event: "set-tier-weight", token-id: token-id, weight: weight })
122 : 8442 : (ok true)
123 : : )
124 : : )
125 : :
126 : : ;; ------------------------
127 : : ;; Mint / Burn
128 : : ;; ------------------------
129 : 356 : (define-public (mint (recipient principal) (token-id uint) (amount uint))
130 : 700 : (let (
131 : 700 : (current-epoch (/ burn-block-height epoch-duration))
132 : 0 : (base-amount
133 : 700 : (if (not is-in-mainnet)
134 [ + ]: 700 : (* amount u2) ;; testnet: 2x
135 [ - ]: 0 : (if (< burn-block-height (+ (var-get launch-height) u12000))
136 [ - ]: 0 : (/ (* amount u3) u2) ;; 1.5x on early mainnet
137 [ - ]: 0 : amount))) ;; no early adopter bonus
138 : 700 : (weight (default-to u1 (map-get? tier-weights token-id)))
139 : 700 : (weighted-amount (* base-amount weight))
140 : : )
141 : 700 : (begin
142 : 700 : (try! (is-dao-or-extension))
143 [ - ]: 698 : (asserts! (> base-amount u0) err-zero-amount)
144 [ - ][ + + ]: 698 : (asserts! (and (> token-id u0) (<= token-id max-tier)) err-invalid-tier)
145 : :
146 : 698 : (try! (ft-mint? bigr-token base-amount recipient))
147 : 698 : (try! (tag-nft { token-id: token-id, owner: recipient }))
148 : 698 : (map-set balances { token-id: token-id, owner: recipient }
149 : 698 : (+ base-amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
150 : 698 : (map-set supplies token-id (+ base-amount (default-to u0 (map-get? supplies token-id))))
151 : 698 : (var-set overall-supply (+ (var-get overall-supply) base-amount))
152 : :
153 : : ;; track epoch joined to avoid overpaying rewards
154 : 698 : (if (is-none (map-get? join-epoch { who: recipient }))
155 [ + ]: 505 : (map-set join-epoch { who: recipient } current-epoch)
156 [ + ]: 193 : true)
157 : :
158 : : ;; update minted for this epoch
159 : 698 : (map-set minted-in-epoch { epoch: current-epoch }
160 : 698 : (+ weighted-amount (default-to u0 (map-get? minted-in-epoch { epoch: current-epoch }))))
161 : :
162 : 698 : (map-set minted-in-epoch-by { epoch: current-epoch, who: recipient }
163 : 698 : (+ weighted-amount (default-to u0 (map-get? minted-in-epoch-by { epoch: current-epoch, who: recipient }))))
164 : :
165 : 698 : (print { event: "sft_mint", token-id: token-id, amount: base-amount, recipient: recipient })
166 : 698 : (ok true)
167 : : )
168 : : )
169 : : )
170 : :
171 : 4 : (define-public (burn (owner principal) (token-id uint) (amount uint))
172 : 5 : (let (
173 : 5 : (current-epoch (/ burn-block-height epoch-duration))
174 : 5 : (weight (default-to u1 (map-get? tier-weights token-id)))
175 : 5 : (weighted-amount (* amount weight))
176 : 5 : (current (default-to u0 (map-get? balances { token-id: token-id, owner: owner })))
177 : : )
178 : 5 : (begin
179 : 5 : (try! (is-dao-or-extension))
180 [ - ]: 3 : (asserts! (>= current amount) err-insufficient-balance)
181 : 3 : (try! (ft-burn? bigr-token amount owner))
182 : 3 : (map-set balances { token-id: token-id, owner: owner } (- current amount))
183 : 3 : (map-set supplies token-id (- (unwrap-panic (get-total-supply token-id)) amount))
184 : 3 : (var-set overall-supply (- (var-get overall-supply) amount))
185 : :
186 : : ;; Decrement epoch-level mint counters
187 : 3 : (let (
188 : 3 : (prev-total (default-to u0 (map-get? minted-in-epoch { epoch: current-epoch })))
189 : 3 : (prev-user (default-to u0 (map-get? minted-in-epoch-by { epoch: current-epoch, who: owner })))
190 : : )
191 : 3 : (map-set minted-in-epoch { epoch: current-epoch }
192 [ + - ]: 3 : (if (> prev-total weighted-amount) (- prev-total weighted-amount) u0))
193 : 3 : (map-set minted-in-epoch-by { epoch: current-epoch, who: owner }
194 [ + - ]: 3 : (if (> prev-user weighted-amount) (- prev-user weighted-amount) u0))
195 : : )
196 : :
197 : 3 : (try! (nft-burn? bigr-id { token-id: token-id, owner: owner } owner))
198 : 3 : (print { event: "sft_burn", token-id: token-id, amount: amount, sender: owner })
199 : 3 : (ok true)
200 : : )
201 : : )
202 : : )
203 : :
204 : : ;; ------------------------
205 : : ;; Transfer (DAO-only)
206 : : ;; ------------------------
207 : 6 : (define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
208 : 14 : (begin
209 : 14 : (try! (is-dao-or-extension))
210 [ - ]: 12 : (asserts! (> amount u0) err-zero-amount)
211 : 12 : (let (
212 : 12 : (sender-balance (default-to u0 (map-get? balances { token-id: token-id, owner: sender })))
213 : 12 : (weight (default-to u1 (map-get? tier-weights token-id)))
214 : 12 : (weighted-amount (* amount weight))
215 : 12 : (epoch (/ burn-block-height epoch-duration))
216 : : )
217 [ - ]: 12 : (asserts! (>= sender-balance amount) err-insufficient-balance)
218 : 12 : (try! (ft-transfer? bigr-token amount sender recipient))
219 : 12 : (try! (tag-nft { token-id: token-id, owner: sender }))
220 : 12 : (try! (tag-nft { token-id: token-id, owner: recipient }))
221 : 12 : (map-set balances { token-id: token-id, owner: sender } (- sender-balance amount))
222 : 12 : (map-set balances { token-id: token-id, owner: recipient }
223 : 12 : (+ amount (default-to u0 (map-get? balances { token-id: token-id, owner: recipient }))))
224 : :
225 : : ;; adjust minted-in-epoch-by so transferred tokens stay new for this epoch
226 : 12 : (let (
227 : 12 : (sender-prev (default-to u0 (map-get? minted-in-epoch-by {epoch: epoch, who: sender})))
228 : 12 : (recipient-prev (default-to u0 (map-get? minted-in-epoch-by {epoch: epoch, who: recipient})))
229 : : )
230 : 12 : (map-set minted-in-epoch-by {epoch: epoch, who: sender}
231 [ + - ]: 12 : (if (> sender-prev weighted-amount) (- sender-prev weighted-amount) u0))
232 : 12 : (map-set minted-in-epoch-by {epoch: epoch, who: recipient}
233 : 12 : (+ recipient-prev weighted-amount))
234 : : )
235 : :
236 : 12 : (print { event: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient })
237 : 12 : (ok true)
238 : : )
239 : : )
240 : : )
241 : :
242 : 1 : (define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
243 : 2 : (begin
244 : 2 : (try! (transfer token-id amount sender recipient))
245 : 2 : (print memo)
246 : 2 : (ok true)
247 : : )
248 : : )
249 : :
250 : 2 : (define-public (transfer-many (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal })))
251 : 2 : (fold transfer-many-iter transfers (ok true))
252 : : )
253 : :
254 : 1 : (define-public (transfer-many-memo (transfers (list 200 { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) })))
255 : 1 : (fold transfer-many-memo-iter transfers (ok true))
256 : : )
257 : :
258 : : ;; -------------------------
259 : : ;; Claims for big from bigr
260 : : ;; individual and batch supported..
261 : : ;; -------------------------
262 : :
263 : 9 : (define-public (claim-big-reward)
264 : 51 : (claim-big-reward-for-user tx-sender)
265 : : )
266 : :
267 : 0 : (define-public (claim-big-reward-batch (users (list 100 principal)))
268 : 0 : (fold claim-big-reward-batch-iter users (ok u0))
269 : : )
270 : :
271 : 0 : (define-private (claim-big-reward-batch-iter (user principal) (acc (response uint uint)))
272 : 0 : (let (
273 : 0 : (a (unwrap! acc err-claims-zero-total))
274 : 0 : (b (unwrap! (claim-big-reward-for-user user) err-claims-zero-total))
275 : : )
276 : 0 : (ok (+ a b))
277 : : )
278 : : )
279 : :
280 : 9 : (define-private (claim-big-reward-for-user (user principal)) ;; returns share or u0
281 : 51 : (let (
282 : 51 : (epoch (/ burn-block-height epoch-duration))
283 : 51 : (claim-epoch (get-latest-claimable-epoch))
284 : 51 : (last (default-to u0 (map-get? last-claimed-epoch { who: user })))
285 : 51 : (joined (default-to epoch (map-get? join-epoch { who: user }))) ;; epoch they joined
286 : 51 : (total-live (unwrap! (get-weighted-supply) err-claims-zero-total))
287 : 51 : (minted-this-epoch (default-to u0 (map-get? minted-in-epoch { epoch: epoch })))
288 : 51 : (total (- total-live minted-this-epoch))
289 : : )
290 [ + + ]: 51 : (if (and (< last claim-epoch) (> claim-epoch joined))
291 [ + ]: 32 : (let (
292 : 32 : (user-weighted-rep (unwrap! (get-weighted-rep user) err-claims-zero-rep))
293 : 32 : (user-minted-this-epoch (default-to u0 (map-get? minted-in-epoch-by { epoch: epoch, who: user })))
294 : 32 : (rep (if (> user-weighted-rep user-minted-this-epoch)
295 [ + ]: 32 : (- user-weighted-rep user-minted-this-epoch)
296 [ - ]: 0 : u0))
297 : : )
298 [ + + ]: 32 : (if (and (> rep u0) (> total u0))
299 [ + ]: 32 : (let (
300 : 32 : (share-scaled (/ (* (* rep (var-get reward-per-epoch)) SCALE) total))
301 : 32 : (share (/ share-scaled SCALE))
302 : : )
303 : 32 : (map-set last-claimed-epoch { who: user } claim-epoch)
304 : 32 : (try! (contract-call? .bme006-0-treasury sip010-transfer share user none .bme000-0-governance-token))
305 : 31 : (print { event: "big-claim", user: user, epoch: epoch, amount: share, reward-per-epoch: (var-get reward-per-epoch) })
306 : 31 : (ok share)
307 : : )
308 [ - ]: 0 : (ok u0)
309 : : )
310 : : )
311 [ + ]: 19 : (ok u0)
312 : : )
313 : : )
314 : : )
315 : :
316 : : ;; ------------------------
317 : : ;; Helpers
318 : : ;; ------------------------
319 : 354 : (define-private (tag-nft (nft-token-id { token-id: uint, owner: principal }))
320 : 722 : (begin
321 : 722 : (if (is-some (nft-get-owner? bigr-id nft-token-id))
322 [ + ]: 186 : (try! (nft-burn? bigr-id nft-token-id (get owner nft-token-id)))
323 [ + ]: 536 : true)
324 : 722 : (nft-mint? bigr-id nft-token-id (get owner nft-token-id))
325 : : )
326 : : )
327 : :
328 : 2 : (define-private (transfer-many-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal }) (prev (response bool uint)))
329 [ + - ]: 6 : (match prev ok-prev (transfer (get token-id item) (get amount item) (get sender item) (get recipient item)) err-prev prev)
330 : : )
331 : :
332 : 1 : (define-private (transfer-many-memo-iter (item { token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34) }) (prev (response bool uint)))
333 [ + - ]: 2 : (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)
334 : : )
335 : :
336 : : ;; dynamic weighted totals for user
337 : 7 : (define-read-only (get-weighted-rep (user principal))
338 : 32 : (let (
339 : 32 : (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20))
340 : 32 : (result (fold add-weighted-rep-for-user tiers (tuple (acc u0) (user user))))
341 : : )
342 : 32 : (ok (get acc result))
343 : : )
344 : : )
345 : :
346 : 7 : (define-private (add-weighted-rep-for-user (token-id uint) (state (tuple (acc uint) (user principal))))
347 : 640 : (let (
348 : 640 : (acc (get acc state))
349 : 640 : (user (get user state))
350 : 640 : (bal-at-tier (default-to u0 (map-get? balances {token-id: token-id, owner: user})))
351 : 640 : (weight-at-tier (default-to u1 (map-get? tier-weights token-id)))
352 : : )
353 : 640 : (tuple (acc (+ acc (* bal-at-tier weight-at-tier))) (user user))
354 : : )
355 : : )
356 : :
357 : : ;; dynamic weighted totals for overall supply pool
358 : 10 : (define-read-only (get-weighted-supply)
359 : 52 : (let (
360 : 52 : (tiers (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20))
361 : 52 : (result (fold add-weighted-supply-for-tier tiers u0))
362 : : )
363 : 52 : (ok result)
364 : : )
365 : : )
366 : :
367 : 10 : (define-private (add-weighted-supply-for-tier (token-id uint) (acc uint))
368 : 1040 : (let (
369 : 1040 : (tier-supply (default-to u0 (map-get? supplies token-id)))
370 : 1040 : (weight (default-to u1 (map-get? tier-weights token-id)))
371 : : )
372 : 1040 : (+ acc (* tier-supply weight))
373 : : )
374 : : )
375 : :
376 : : ;; --- Extension callback
377 : :
378 : 1 : (define-public (callback (sender principal) (memo (buff 34)))
379 : 1 : (ok true)
380 : : )
|