Smart Contract

MikoSeaMarket

A.0b80e42aaab305f0.MikoSeaMarket

Deployed

16h ago
Feb 28, 2026, 02:29:56 AM UTC

Dependents

0 imports
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