/
dartest.clar
220 lines (194 loc) · 9.51 KB
/
dartest.clar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
;; -----------------------------------------------------------------------------
;; M A R K E T P L A C E by <redacted>
;; -----------------------------------------------------------------------------
;;(use-trait nft-trait .nft-trait.nft-trait) ;; SIP-009 NFT trait
;;(impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait) ;; testnet, devnet, Clarity console
(use-trait nft-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) ;; mainnet
;;(use-trait nft-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait) ;; testnet, devnet, Clarity console
(define-constant ERR_UNAUTHORIZED (err u401))
(define-constant ERR_UNWRAP_GET_OWNER (err u402))
(define-constant ERR_TRANSFER_TO_ESCROW (err u403))
(define-constant ERR_TRANSFER_TO_BUYER (err u404))
(define-constant ERR_NFT_NOT_FOR_SALE (err u405))
(define-constant ERR_PAY_SELLER (err u406))
(define-constant ERR_PAY_COMMISSION (err u407))
(define-constant ERR_PAY_ROYALTY (err u408))
(define-constant ERR_NFT_NOT_WHITELISTED (err u409))
(define-constant MARKETPLACE_OWNER tx-sender) ;; <enter marketplace address here>
(define-constant CONTRACT_PRINCIPAL (as-contract tx-sender))
(define-data-var marketplace-commission-rate uint u0) ;; current marketplace commission rate
(define-map Listings
{ ;; listing-key
list-nft-contract: principal,
list-nft-id: uint
}
{ ;;listing-value
list-nft-owner: principal,
list-price: uint, ;; unit price in STX token, to be refactored to accept any whitelisted SIP-010 token.
list-commission-amount: uint, ;; sales commission paid to marketplace owner. pre-calculated during NFT listing creation.
list-royalty-address: principal, ;; creator of the NFT collection.
list-royalty-amount: uint ;; royalties paid to the creator. pre-calculated during NFT listing creation.
;; Amounts will be precalculated, meaning commissions and royalties will never change once NFT is listed.
;; If the NFT collection creator decided to change the royalty percentage, the existing
;; listing will not be affected. This avoids customer confusion and possible support nightmare.
;; Same is applied to commission.
}
)
(define-map WhitelistNftCollection
{ whitelist-nft: principal } ;; NFT contract
{
royalty-address: principal, ;; address to receive royalty fees
royalty-rate: uint, ;; royalty percentage rate
royalty-minimum: uint, ;; placeholder for now, in case needed.
royalty-maximum: uint, ;; placeholder for now, in case needed.
listing-minimum: uint, ;; listing minimum price. placeholder for now, in case needed.
collection-owner: principal ;; NFT collection owner, to enable self-maintain (e.g. change royalty amount). placeholder for now.
}
)
;; ----------------------------------------------------------------------------
;; Add to map. Use map-set to override existing listing entry.
;; List an NFT into the marketplace:
;; 1. Check if tx-sender owns the NFT to be listed.
;; 2. Check if NFT is part of whitelisted NFT collection.
;; 3. Get/set corresponding royalty values from collection whitelist.
;; 4. Get/set marketplace commission rate.
;; ----------------------------------------------------------------------------
(define-public (list-nft (nft-contract <nft-trait>) (nft-id uint) (price uint))
(let
(
(nft-owner (unwrap! (get-owner nft-contract nft-id) ERR_UNWRAP_GET_OWNER))
;; ---- get whitelist info ---
(whitelist-key {
whitelist-nft: (contract-of nft-contract)
})
(whitelist-value (unwrap! (map-get? WhitelistNftCollection whitelist-key) ERR_NFT_NOT_WHITELISTED))
(royalty-address (get royalty-address whitelist-value))
(royalty-rate (get royalty-rate whitelist-value))
(royalty-amount (/ (* price royalty-rate) u100))
(commission-amount (/ (* price (var-get marketplace-commission-rate)) u100))
;; include here sip-010 ft tokens for payment - logic is very similar to whitelisted nft
;; ---- set listing info ---
(listing-key {
list-nft-contract: (contract-of nft-contract),
list-nft-id: nft-id
})
(listing-value {
list-nft-owner: tx-sender, ;; succeeding asserts! fails if tx-sender not nft owner
list-price: price,
list-commission-amount: commission-amount,
list-royalty-address: royalty-address, ;; nft collection whitelisted address
list-royalty-amount: royalty-amount
})
)
(asserts! (is-eq nft-owner tx-sender) ERR_UNAUTHORIZED)
(map-set Listings listing-key listing-value) ;; add NFT to Listings map, overwrite duplicate key.
(try! (escrow-nft-to-marketplace nft-contract nft-id))
(print {
action: "list-nft",
payload: {
nft-contract: nft-contract,
nft-id: nft-id,
price: price
}
})
(ok (merge listing-key listing-value)) ;; return to caller
)
)
;; ----------------------------------------------------------------------------
;; Buy NFT
;; 1. Check if NFT is in the listings.
;; 2. Transfer FT from tx-sender/buyer to:
;; - seller
;; - royalty fees to NFT creator
;; - marketplace commission
;; 3. Transfer NFT from escrow (contract principal) to buyer
;; 4. Delete NFT from map Listings.
;; 5. Congrats and enjoy your NFT! :)
;; ----------------------------------------------------------------------------
(define-public (buy-nft (nft-contract <nft-trait>) (nft-id uint))
(let
(
(listing-key {
list-nft-contract: (contract-of nft-contract),
list-nft-id: nft-id
})
(listing-value (unwrap! (map-get? Listings listing-key) ERR_NFT_NOT_FOR_SALE))
(list-nft-owner (get list-nft-owner listing-value))
(list-price (get list-price listing-value))
(list-commission-amount (get list-commission-amount listing-value))
(list-royalty-address (get list-royalty-address listing-value))
(list-royalty-amount (get list-royalty-amount listing-value))
(pay-to-seller-amount (- (- list-price list-commission-amount) list-royalty-amount))
)
(unwrap! (stx-transfer? pay-to-seller-amount tx-sender list-nft-owner) ERR_PAY_SELLER )
(unwrap! (stx-transfer? list-commission-amount tx-sender MARKETPLACE_OWNER) ERR_PAY_COMMISSION)
(unwrap! (stx-transfer? list-royalty-amount tx-sender list-royalty-address) ERR_PAY_ROYALTY)
(try! (transfer-nft-to-buyer nft-contract nft-id))
(map-delete Listings listing-key)
(print {
action: "buy-nft",
payload: {
nft-contract: nft-contract,
nft-id: nft-id
},
info: {
price: list-price,
list-commission-amount: list-commission-amount,
list-royalty-amount: list-royalty-amount,
pay-to-seller-amount: pay-to-seller-amount
}
})
(ok listing-key)
)
)
(define-private (get-owner (nft <nft-trait>) (nft-id uint))
(unwrap-panic (contract-call? nft get-owner nft-id)) ;; need to test this unwrap-panic scenario
)
(define-private (escrow-nft-to-marketplace (nft-contract <nft-trait>) (nft-id uint))
(begin
(unwrap! (contract-call? nft-contract transfer nft-id tx-sender CONTRACT_PRINCIPAL) ERR_TRANSFER_TO_ESCROW)
(ok true)
)
)
(define-private (transfer-nft-to-buyer (nft-contract <nft-trait>) (nft-id uint))
(let ((buyer tx-sender))
(as-contract (contract-call? nft-contract transfer nft-id CONTRACT_PRINCIPAL buyer))
)
)
;; ------------------------------------------------------------------
;; NFT Collection Whitelist functions
;; ------------------------------------------------------------------
(define-public (add-whitelist-collection (nft-contract principal) (royalty-rate uint) (royalty-address principal))
(begin
(asserts! (is-eq tx-sender MARKETPLACE_OWNER) ERR_UNAUTHORIZED) ;; guard rail
(map-set WhitelistNftCollection
{ whitelist-nft: nft-contract }
{
royalty-address: royalty-address, ;; hardcode to tx-sender for now
royalty-rate: royalty-rate,
royalty-minimum: u0,
royalty-maximum: u0,
listing-minimum: u0,
collection-owner: MARKETPLACE_OWNER ;; hardcode for now
}
)
(ok true)
)
)
;; ------------------------------------------------------------------
;; Following lines executed at deployment time.
;; ------------------------------------------------------------------
(var-set marketplace-commission-rate u5) ;; initial commission rate, can be updated by marketplace owner
;;(try! (add-whitelist-collection 'SP32AEEF6WW5Y0NMJ1S8SBSZDAY8R5J32NBZFPKKZ.free-punks-v0 u10))
;;(try! (add-whitelist-collection 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-cat-in-the-hat u50 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM))
(try! (add-whitelist-collection 'ST1S06MKADCKV8B18PZYC3F64CHQR2G9YPJ83BGB2.kids-nft u50 'ST1XBS03PFDTV1HSD7BY02V6VG16VTNRDP8GEN2VK))
(print {
action: "At deployment!",
marketplace-commission-rate: (var-get marketplace-commission-rate)
})
;; ------------------------------------------------------------------
;; For testing only
;; ------------------------------------------------------------------
(define-read-only (echo (shout-out (string-ascii 100))) (ok (concat "Right back at you - " shout-out))) ;; echo
(define-read-only (get-contract-caller) (ok contract-caller )) ;; returns contract-caller
(define-read-only (get-tx-sender) (ok tx-sender )) ;; returns tx-sender