Repository navigation
Expand file tree
/
Copy pathnft.cell
More file actions
229 lines (200 loc) · 9.99 KB
/
Copy pathnft.cell
File metadata and controls
229 lines (200 loc) · 9.99 KB
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
221
222
223
224
225
226
227
228
229
module cellscript::nft
use cellscript::fungible_token::Token
const U64_MAX: u64 = 18446744073709551615
const MAX_SUPPLY: u64 = 10000
const ROYALTY_BASIS_POINTS: u16 = 250
struct Metadata {
name: String,
description: String,
image_uri: String,
attributes: Vec<(String, String)>,
}
resource NFT has store, create, consume, replace, burn, relock, read_ref {
collection_id: Hash,
token_id: u64,
owner: Address,
metadata_hash: Hash,
royalty_recipient: Address,
royalty_bps: u16,
}
resource Collection has store, create, replace {
name: String,
symbol: String,
creator: Address,
total_supply: u64,
max_supply: u64,
base_uri: String,
}
receipt Listing has create, consume, burn {
collection_id: Hash,
token_id: u64,
seller: Address,
price: u64,
created_at: u64,
state: u8,
}
receipt Offer has create, consume, burn {
collection_id: Hash,
token_id: u64,
buyer: Address,
price: u64,
expires_at: u64,
payment_symbol: [u8; 8],
state: u8,
}
action create_collection(creator: Address, max_supply: u64, name: String, symbol: String, base_uri: String) -> collection: Collection {
verification
require max_supply > 0, "Max supply must be positive"
require max_supply <= MAX_SUPPLY, "Max supply too high"
create collection = Collection { name, symbol, creator, total_supply: 0, max_supply, base_uri } with_lock(creator)
}
action mint(collection_before: Collection, to: Address, metadata_hash: Hash) -> (collection_after: Collection, nft: NFT) {
transition collection_before -> collection_after
verification
require collection_before.total_supply < collection_before.max_supply, "Max supply reached"
let token_id = collection_before.total_supply + 1
preserve collection_after from collection_before {
name
symbol
creator
max_supply
base_uri
}
require collection_after.total_supply == token_id
create nft = NFT { collection_id: collection_before.type_hash(), token_id, owner: to, metadata_hash, royalty_recipient: collection_before.creator, royalty_bps: ROYALTY_BASIS_POINTS }
}
action transfer(nft_before: NFT, to: Address) -> nft_after: NFT {
transition nft_before -> nft_after
verification
require nft_before.owner != to, "Cannot transfer to self"
preserve nft_after from nft_before {
collection_id
token_id
metadata_hash
royalty_recipient
royalty_bps
}
require nft_after.owner == to
}
action create_listing(read nft: NFT, price: u64) -> listing: Listing {
verification
let now = ckb::epoch_number_to_u64(ckb::header_dep(0).epoch_number)
require price > 0, "Price must be positive"
create listing = Listing { collection_id: nft.collection_id, token_id: nft.token_id, seller: nft.owner, price, created_at: now, state: 0 } with_lock(nft.owner)
}
action cancel_listing(listing: Listing) {
verification
destroy listing
}
action buy_from_listing(nft_before: NFT, listing: Listing, buyer: Address, royalty_payment: Token, seller_payment: Token) -> (nft_after: NFT, royalty_payout: Token, seller_payout: Token) {
transition nft_before -> nft_after
verification
require listing.collection_id == nft_before.collection_id, "Collection mismatch"
require listing.token_id == nft_before.token_id, "Token mismatch"
require listing.seller == nft_before.owner, "Seller is not current owner"
require seller_payment.amount <= U64_MAX - royalty_payment.amount, "Payment overflow"
require royalty_payment.amount + seller_payment.amount == listing.price, "Payment must equal listing price"
require royalty_payment.symbol == seller_payment.symbol, "Payment token mismatch"
require nft_before.royalty_bps <= 1000, "Royalty too high"
let royalty_divisor = if nft_before.royalty_bps == 0 { 1 } else { nft_before.royalty_bps as u64 }
require nft_before.royalty_bps == 0 || listing.price <= U64_MAX / royalty_divisor, "Royalty overflow"
let royalty_amount = listing.price * nft_before.royalty_bps as u64 / 10000
require royalty_payment.amount == royalty_amount, "Incorrect royalty payment"
require seller_payment.amount == listing.price - royalty_amount, "Incorrect seller payment"
preserve nft_after from nft_before {
collection_id
token_id
metadata_hash
royalty_recipient
royalty_bps
}
require nft_after.owner == buyer
consume royalty_payment
consume seller_payment
destroy listing
create royalty_payout = Token { amount: royalty_payment.amount, symbol: royalty_payment.symbol } with_lock(nft_before.royalty_recipient)
create seller_payout = Token { amount: seller_payment.amount, symbol: seller_payment.symbol } with_lock(nft_before.owner)
}
action create_offer(collection_id: Hash, token_id: u64, buyer: Address, price: u64, payment_symbol: [u8; 8], expires_at: u64) -> offer: Offer {
verification
let current_epoch = ckb::header_dep(0).epoch_number
let now = ckb::epoch_number_to_u64(current_epoch)
let current_since = ckb::since_absolute_epoch(now, 0, 1)
let expiry_since = ckb::since_absolute_epoch(expires_at, 0, 1)
require price > 0, "Price must be positive"
require expiry_since > current_since, "Expiration must be in the future"
create offer = Offer { collection_id, token_id, buyer, price, expires_at, payment_symbol, state: 0 } with_lock(buyer)
}
action accept_offer(nft_before: NFT, offer: Offer, royalty_payment: Token, seller_payment: Token) -> (nft_after: NFT, royalty_payout: Token, seller_payout: Token) {
transition nft_before -> nft_after
verification
let now = ckb::epoch_number_to_u64(ckb::header_dep(0).epoch_number)
require now < offer.expires_at, "Offer expired"
require offer.collection_id == nft_before.collection_id, "Collection mismatch"
require offer.token_id == nft_before.token_id, "Token mismatch"
require seller_payment.amount <= U64_MAX - royalty_payment.amount, "Payment overflow"
require royalty_payment.amount + seller_payment.amount == offer.price, "Payment must equal offer price"
require royalty_payment.symbol == offer.payment_symbol, "Wrong royalty payment token"
require seller_payment.symbol == offer.payment_symbol, "Wrong seller payment token"
require nft_before.royalty_bps <= 1000, "Royalty too high"
let royalty_divisor = if nft_before.royalty_bps == 0 { 1 } else { nft_before.royalty_bps as u64 }
require nft_before.royalty_bps == 0 || offer.price <= U64_MAX / royalty_divisor, "Royalty overflow"
let royalty_amount = offer.price * nft_before.royalty_bps as u64 / 10000
require royalty_payment.amount == royalty_amount, "Incorrect royalty payment"
require seller_payment.amount == offer.price - royalty_amount, "Incorrect seller payment"
preserve nft_after from nft_before {
collection_id
token_id
metadata_hash
royalty_recipient
royalty_bps
}
require nft_after.owner == offer.buyer
consume royalty_payment
consume seller_payment
destroy offer
create royalty_payout = Token { amount: royalty_payment.amount, symbol: royalty_payment.symbol } with_lock(nft_before.royalty_recipient)
create seller_payout = Token { amount: seller_payment.amount, symbol: seller_payment.symbol } with_lock(nft_before.owner)
}
action burn(nft: NFT) {
verification
destroy nft
}
action batch_mint(collection_before: Collection, recipients: [Address; 4], metadata_hashes: [Hash; 4]) -> (collection_after: Collection, nft0: NFT, nft1: NFT, nft2: NFT, nft3: NFT) {
transition collection_before -> collection_after
verification
require collection_before.total_supply + 4 <= collection_before.max_supply, "Max supply would be exceeded"
let first_token_id = collection_before.total_supply + 1
preserve collection_after from collection_before {
name
symbol
creator
max_supply
base_uri
}
require collection_after.total_supply == collection_before.total_supply + 4
create nft0 = NFT { collection_id: collection_before.type_hash(), token_id: first_token_id, owner: recipients[0], metadata_hash: metadata_hashes[0], royalty_recipient: collection_before.creator, royalty_bps: ROYALTY_BASIS_POINTS }
create nft1 = NFT { collection_id: collection_before.type_hash(), token_id: first_token_id + 1, owner: recipients[1], metadata_hash: metadata_hashes[1], royalty_recipient: collection_before.creator, royalty_bps: ROYALTY_BASIS_POINTS }
create nft2 = NFT { collection_id: collection_before.type_hash(), token_id: first_token_id + 2, owner: recipients[2], metadata_hash: metadata_hashes[2], royalty_recipient: collection_before.creator, royalty_bps: ROYALTY_BASIS_POINTS }
create nft3 = NFT { collection_id: collection_before.type_hash(), token_id: first_token_id + 3, owner: recipients[3], metadata_hash: metadata_hashes[3], royalty_recipient: collection_before.creator, royalty_bps: ROYALTY_BASIS_POINTS }
}
lock nft_ownership(protected nft: NFT, witness claimed_owner: Address) -> bool {
verification
require nft.owner == claimed_owner
}
lock listing_seller(protected listing: Listing, witness claimed_seller: Address) -> bool {
verification
require listing.seller == claimed_seller
}
lock offer_buyer(protected offer: Offer, witness claimed_buyer: Address) -> bool {
verification
require offer.buyer == claimed_buyer
}
lock valid_royalty(protected nft: NFT) -> bool {
verification
require nft.royalty_bps <= 1000
}
lock collection_creator(protected collection: Collection, witness claimed_creator: Address) -> bool {
verification
require collection.creator == claimed_creator
}