Smart Contract
MikoSeaMarket
A.0b80e42aaab305f0.MikoSeaMarket
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4
5access(all) contract MikoSeaMarket {
6 //------------------------------------------------------------
7 // Events
8 //------------------------------------------------------------
9 access(all) event NFTMikoSeaMarketInitialized()
10 access(all) event MikoSeaMarketInitialized(marketResourceID: UInt64)
11 access(all) event MikoSeaMarketDestroyed(marketResourceID: UInt64)
12 access(all) event OrderCreated(orderId: UInt64, holderAddress: Address, nftType: Type, nftID: UInt64, price: UFix64)
13 access(all) event OrderCompleted(orderId: UInt64, purchased: Bool, holderAddress: Address, buyerAddress: Address?, nftID: UInt64, nftType: Type, price: UFix64)
14
15 //------------------------------------------------------------
16 // Path
17 //------------------------------------------------------------
18 access(all) let AdminStoragePath: StoragePath
19 access(all) let AdminPublicPath: PublicPath
20 access(all) let MarketStoragePath: StoragePath
21 access(all) let MarketPublicPath: PublicPath
22
23 //------------------------------------------------------------
24 // MikoSeaMarket vairables
25 //------------------------------------------------------------
26 access(self) let adminCap: Capability<&{AdminPublicCollection}>
27 access(all) let mikoseaCap: Capability<&{FungibleToken.Receiver}>
28 access(all) let tokenPublicPath: PublicPath
29 // maping transactionId - orderId
30 access(all) let refIds: {String: UInt64}
31 // maping nftID - orderId
32 access(all) let nftIds: {UInt64: UInt64}
33
34 //------------------------------------------------------------
35 // OrderDetail Struct
36 //------------------------------------------------------------
37 access(all) resource OrderDetail {
38 access(all) var purchased: Bool
39
40 // MIKOSEANFT.NFT or MIKOSEANFTV2.NFT
41 access(all) let nftType: Type
42 access(all) let nftID: UInt64
43
44 access(all) let holderCap: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>
45
46 // status:
47 // created
48 // validated
49 // done
50 // reject
51 access(all) var status: String
52
53 // unit yen
54 access(all) let salePrice: UFix64
55
56 // transactionOrderId
57 access(all) var refId: String?
58
59 access(all) var receiverCap: Capability<&{NonFungibleToken.Receiver}>?
60
61 access(all) let royalties: MetadataViews.Royalties
62
63 // unix time
64 access(all) var expireAt: UFix64?
65 // unix time
66 access(all) let createdAt: UFix64
67 // unix time
68 access(all) var purchasedAt: UFix64?
69 // unix time
70 access(all) var cancelAt: UFix64?
71
72 access(all) let metadata: {String:String}
73
74 init (
75 nftType: Type,
76 nftID: UInt64,
77 holderCap: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
78 salePrice:UFix64,
79 royalties: MetadataViews.Royalties,
80 metadata: {String:String}
81 ) {
82 self.holderCap = holderCap
83 self.nftType = nftType
84 self.nftID = nftID
85 self.salePrice = salePrice
86 self.royalties = royalties
87 self.metadata = metadata
88
89 self.refId = nil
90 self.receiverCap = nil
91 self.status = "created"
92 self.purchased = false
93 self.createdAt = getCurrentBlock().timestamp
94 self.purchasedAt = nil
95 self.cancelAt = nil
96 self.expireAt = nil
97
98 self.checkAfterCreate()
99 }
100
101 access(all) fun getId(): UInt64 {
102 return self.uuid
103 }
104
105 access(self) fun checkAfterCreate() {
106 let collection = self.holderCap.borrow() ?? panic("COULD_NOT_BORROW_HOLDER")
107 self.borrowNFT()
108 }
109
110 /// withdraw removes an NFT from the collection and moves it to the caller
111 access(NonFungibleToken.Withdraw) fun withdraw(): @{NonFungibleToken.NFT} {
112 let ref = self.holderCap.borrow() ?? panic("Could not withdraw an NFT with the provided ID from the collection")
113 return <- ref.withdraw(withdrawID: self.nftID)
114 }
115
116 access(contract) fun setToPurchased() {
117 self.purchased = true
118 self.purchasedAt = getCurrentBlock().timestamp
119 self.status = "done"
120 }
121
122 // when GMO payment success
123 access(contract) fun onPaymentSuccess(refId: String, receiverCap: Capability<&{NonFungibleToken.Receiver}>) {
124 self.status = "validated"
125 self.refId = refId
126 self.receiverCap = receiverCap
127 }
128
129 access(self) fun checkBeforePurchase(_ receiverAddress: Address) {
130 let nft = self.borrowNFT()
131 assert(self.purchased == false, message: "ORDER_IS_PURCHASED")
132 assert(self.status == "validated", message: "STATUS_IS_INVALID")
133 assert(self.receiverCap != nil && self.receiverCap!.address == receiverAddress, message: "NOT_RECIPIENT".concat(", receive ").concat(self.receiverCap!.address.toString()).concat(", receive ").concat(receiverAddress.toString()))
134 }
135
136 // access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
137 // return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
138 // }
139
140 access(all) fun borrowNFT(): &{NonFungibleToken.NFT} {
141 let ref = self.holderCap.borrow() ?? panic("ACCOUNT_NOT_SETUP")
142 let nft = ref.borrowNFT(self.nftID) ?? panic("NOT_FOUND_NFT")
143 assert(nft.isInstance(self.nftType), message: "NFT_TYPE_ERROR")
144 assert(nft.id == self.nftID, message: "NFT_ID_ERROR")
145 return nft
146 }
147
148 // access(all) fun borrowNFTSafe(): &NonFungibleToken.NFT? {
149 // if let ref = self.holderCap.borrow() {
150 // if let nft = ref.borrowNFTSafe(id: self.nftID) {
151 // if (nft.isInstance(self.nftType)) {
152 // return nft
153 // }
154 // }
155 // }
156 // return nil
157 // }
158
159 access(all) fun purchase(_ receiverAddress: Address) {
160 self.checkBeforePurchase(receiverAddress)
161 log(self.receiverCap)
162 log(receiverAddress)
163 let receiverRef = self.receiverCap!.borrow() ?? panic("ACCOUNT_NOT_SETUP")
164 receiverRef.deposit(token: <- self.withdraw())
165 self.setToPurchased()
166 emit OrderCompleted(
167 orderId: self.uuid,
168 purchased: self.purchased,
169 holderAddress: self.holderCap.address,
170 buyerAddress: self.receiverCap?.address,
171 nftID: self.nftID,
172 nftType: self.nftType,
173 price: self.salePrice
174 )
175 }
176
177 // destructor
178 // destroy () {
179 // MikoSeaMarket.nftIds.remove(key: self.nftID)
180 // if self.refId != nil {
181 // MikoSeaMarket.refIds.remove(key: self.refId!)
182 // }
183 // if !self.purchased {
184 // emit OrderCompleted(
185 // orderId: self.uuid,
186 // purchased: self.purchased,
187 // holderAddress: self.holderCap.address,
188 // buyerAddress: self.receiverCap?.address,
189 // nftID: self.nftID,
190 // nftType: self.nftType,
191 // price: self.salePrice
192 // )
193 // }
194 // }
195 }
196
197 //------------------------------------------------------------
198 // StorefrontPublic
199 //------------------------------------------------------------
200 access(all) resource interface StorefrontPublic {
201 access(all) fun getIds(): [UInt64]
202 access(all) fun getOrders(): [&OrderDetail]
203 access(all) fun borrowOrder(_ orderId: UInt64): &OrderDetail?
204 }
205 //------------------------------------------------------------
206 // Storefront
207 //------------------------------------------------------------
208 access(all) resource Storefront: StorefrontPublic {
209 access(all) let orderIds: [UInt64]
210
211 init() {
212 self.orderIds = []
213 }
214
215 // get listing ids
216 access(all) fun getIds(): [UInt64] {
217 return self.orderIds
218 }
219
220 access(all) fun getOrders(): [&OrderDetail] {
221 let res: [&OrderDetail] = []
222 for orderId in self.getIds() {
223 if let order = MikoSeaMarket.getAdminRef().borrowOrder(orderId) {
224 res.append(order)
225 }
226 }
227 return res
228 }
229
230 access(all) fun borrowOrder(_ orderId: UInt64): &OrderDetail? {
231 if !self.getIds().contains(orderId) {
232 return nil
233 }
234 return MikoSeaMarket.getAdminRef().borrowOrder(orderId)
235 }
236
237 access(all) fun createOrder(
238 nftType: Type,
239 nftID: UInt64,
240 holderCap: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
241 salePrice: UFix64,
242 royalties: MetadataViews.Royalties,
243 metadata: {String:String}
244 ): UInt64 {
245 let orderId = MikoSeaMarket.getAdminRef().createOrder(nftType: nftType, nftID: nftID, holderCap: holderCap, salePrice: salePrice, royalties: royalties, metadata: metadata)
246 self.orderIds.append(orderId)
247 return orderId
248 }
249
250 access(all) fun removeOrder(_ orderId: UInt64) {
251 if let order = self.borrowOrder(orderId) {
252 MikoSeaMarket.getAdminRef().removeOrder(orderId)
253 }
254 if let orderIdIndex = self.orderIds.firstIndex(of: orderId) {
255 self.orderIds.remove(at: orderIdIndex)
256 }
257 }
258 }
259
260
261 //------------------------------------------------------------
262 // Admin public
263 //------------------------------------------------------------
264 access(all) resource interface AdminPublicCollection {
265 access(all) fun createOrder(
266 nftType: Type,
267 nftID: UInt64,
268 holderCap: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
269 salePrice: UFix64,
270 royalties: MetadataViews.Royalties,
271 metadata: {String:String}
272 ): UInt64
273 access(all) fun removeOrder(_ orderId: UInt64)
274 access(all) fun getIDs(): [UInt64]
275 access(all) fun borrowOrder(_ orderId: UInt64): &OrderDetail?
276 }
277
278 //------------------------------------------------------------
279 // Admin
280 //------------------------------------------------------------
281 access(all) resource Admin: AdminPublicCollection {
282 access(self) var orders: @{UInt64: OrderDetail}
283
284 init() {
285 self.orders <- {}
286 }
287
288 access(all) fun borrowOrder(_ orderId: UInt64): &OrderDetail? {
289 return &self.orders[orderId] as &OrderDetail?
290 }
291
292 access(all) fun getIDs(): [UInt64] {
293 return self.orders.keys
294 }
295
296 access(all) fun createOrder(
297 nftType: Type,
298 nftID: UInt64,
299 holderCap: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
300 salePrice:UFix64,
301 royalties: MetadataViews.Royalties,
302 metadata: {String:String}
303 ): UInt64 {
304 let order <- create OrderDetail(
305 nftType: nftType,
306 nftID: nftID,
307 holderCap: holderCap,
308 salePrice: salePrice,
309 royalties: royalties,
310 metadata: metadata
311 )
312
313 let orderId = order.uuid
314
315 let oldOrder <- self.orders[orderId] <- order
316 destroy oldOrder
317
318 MikoSeaMarket.nftIds.insert(key: nftID, orderId)
319
320 emit OrderCreated(orderId: orderId, holderAddress: holderCap.address, nftType: nftType, nftID: nftID, price: salePrice)
321
322 return orderId
323 }
324
325 access(all) fun cleanup(orderId: UInt64, holderCap: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>) {
326 let order <- self.orders.remove(key: orderId) ?? panic("NOT_FOUND_ORDER")
327 assert(order.purchased == true, message: "ORDER_IS_PURCHASED")
328 let holderCapBorrow = holderCap.borrow() ?? panic("NOT_FOUND_HOLDER")
329 let nft = holderCapBorrow.borrowNFT(order.nftID) ?? panic("NOT_FOUND_NFT")
330 assert(nft.isInstance(order.nftType), message: "NFT_TYPE_ERROR")
331 assert(nft.id == order.nftID, message: "NFT_ID_ERROR")
332 destroy order
333 }
334
335 access(all) fun cleanAll() {
336 for orderId in self.orders.keys {
337 let order <- self.orders.remove(key: orderId) ?? panic("NOT_FOUND_ORDER")
338 destroy order
339 }
340 }
341
342 access(all) fun removeOrder(_ orderId: UInt64) {
343 let order <- self.orders.remove(key: orderId)
344 ?? panic("NOT_FOUND_ORDER")
345 MikoSeaMarket.nftIds.remove(key: order.nftID)
346 if order.refId != nil {
347 MikoSeaMarket.refIds.remove(key: order.refId!)
348 }
349 if !order.purchased {
350 emit OrderCompleted(
351 orderId: order.uuid,
352 purchased: order.purchased,
353 holderAddress: order.holderCap.address,
354 buyerAddress: order.receiverCap?.address,
355 nftID: order.nftID,
356 nftType: order.nftType,
357 price: order.salePrice
358 )
359 }
360 destroy order
361 }
362
363 // when GMO payment success
364 access(all) fun onPaymentSuccess(orderId: UInt64, refId: String, receiverCap: Capability<&{NonFungibleToken.Receiver}>) {
365 let order = self.borrowOrder(orderId)!
366 order.onPaymentSuccess(refId: refId, receiverCap: receiverCap)
367 MikoSeaMarket.refIds[refId] = order.uuid
368 }
369
370 // admin transfer nft to user
371 access(all) fun purchaseForUser(_ orderId: UInt64, receiverAddress: Address) {
372 let order = self.borrowOrder(orderId)
373 order!.purchase(receiverAddress)
374 }
375
376 // destroy () {
377 // destroy self.orders
378 // emit MikoSeaMarketDestroyed(marketResourceID: self.uuid)
379 // }
380 }
381
382 //------------------------------------------------------------
383 // Contract fun
384 //------------------------------------------------------------
385
386 //------------------------------------------------------------
387 // Create Empty Collection
388 //------------------------------------------------------------
389 access(all) fun createStorefront(): @Storefront {
390 return <-create Storefront()
391 }
392
393 access(self) fun getAdminRef(): &{AdminPublicCollection}{
394 return self.adminCap.borrow() ?? panic("NOT_FOUND_ADMIN")
395 }
396
397 access(all) fun getIDs(): [UInt64] {
398 return self.getAdminRef().getIDs()
399 }
400
401 access(all) fun borrowOrder(_ orderId: UInt64): &OrderDetail? {
402 return self.getAdminRef().borrowOrder(orderId)
403 }
404
405 access(all) fun getAdminAddress(): Address {
406 return self.adminCap.address
407 }
408
409 // access(all) fun purchase(orderId: UInt64, receiverCap: Capability<&{NonFungibleToken.Receiver}>) {
410 // return self.getAdminRef().purchase(orderId: orderId, receiverCap: receiverCap)
411 // }
412
413 //------------------------------------------------------------
414 // Initializer
415 //------------------------------------------------------------
416 init () {
417 let isMainnetAccount = self.account.address == Address(0x0b80e42aaab305f0)
418 if isMainnetAccount {
419 self.AdminStoragePath = /storage/MarketAdmin
420 self.AdminPublicPath = /public/MarketAdmin
421 self.MarketStoragePath = /storage/MikoSeaMarket
422 self.MarketPublicPath = /public/MikoSeaMarket
423 } else {
424 self.AdminStoragePath = /storage/TestMarketAdmin
425 self.AdminPublicPath = /public/TestMarketAdmin
426 self.MarketStoragePath = /storage/TestMikoSeaMarket
427 self.MarketPublicPath = /public/TestMikoSeaMarket
428 }
429 self.refIds = {}
430 self.nftIds = {}
431 self.tokenPublicPath = /public/flowTokenReceiver
432 let tokenStoragePath = /storage/flowTokenVault
433
434 self.mikoseaCap = self.account.capabilities.storage.issue<&{FungibleToken.Receiver}>(tokenStoragePath)
435 // Create a Collection resource and save it to storage
436 let collection <- create Admin()
437 self.account.storage.save(<-collection, to: self.AdminStoragePath)
438 // create a public capability for the collection
439 self.adminCap = self.account.capabilities.storage.issue<&{AdminPublicCollection}>(self.AdminStoragePath)
440 self.account.capabilities.publish(self.adminCap, at: self.AdminPublicPath)
441
442 emit NFTMikoSeaMarketInitialized()
443 }
444}
445