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