Smart Contract

MikoSeaNftAuctionV2

A.0b80e42aaab305f0.MikoSeaNftAuctionV2

Deployed

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

Dependents

0 imports
1// This contract allows users to put their NFTs up for sale. Other users
2// can purchase these NFTs with fungible tokens.
3
4import FungibleToken from 0xf233dcee88fe0abe
5import NonFungibleToken from 0x1d7e57aa55817448
6import MetadataViews from 0x1d7e57aa55817448
7import MIKOSEANFT from 0x0b80e42aaab305f0
8import FlowToken from 0x1654653399040a61
9import MikoSeaUtility from 0x0b80e42aaab305f0
10import Burner from 0xf233dcee88fe0abe
11
12access(all) contract MikoSeaNftAuctionV2 {
13
14    // storage auction history
15    access(all) struct BidItem {
16        access(all) let bidId: UInt64
17        access(all) let auctionId: UInt64
18        // bidder address
19        access(all) let address: Address
20        access(all) let bidPrice: UFix64
21        // created time
22        access(all) let bidTime: UFix64
23
24        init(auctionId: UInt64, address: Address, bidPrice: UFix64, bidTime: UFix64?) {
25            self.address = address
26            self.bidPrice = bidPrice
27            self.bidTime = bidTime ?? getCurrentBlock().timestamp
28            self.auctionId = auctionId
29            self.bidId = MikoSeaNftAuctionV2.bidNumber[auctionId]!
30        }
31    }
32
33    access(all) struct BidWinner {
34        access(all) let bidItem: BidItem
35        // created time
36        access(all) let lastTimeCanPay: UFix64
37
38        init(bidItem: BidItem, lastTimeCanPay: UFix64) {
39            self.bidItem = bidItem
40            self.lastTimeCanPay = lastTimeCanPay
41        }
42    }
43
44    // This struct aggreates status for the auction and is exposed in order to create websites using auction information
45    access(all) struct AuctionInfo {
46        access(all) let auctionId: UInt64
47
48        // Yen
49        access(all) let price : UFix64
50        access(all) let minimumBidIncrement : UFix64
51        access(all) let numberOfBids : Int
52        //Active is probably not needed when we have completed and expired above, consider removing it
53        access(all) let active: Bool
54        access(all) let auctionStatus: AuctionStatusEnum
55        access(all) let auctionEndTime : UFix64
56        access(all) let auctionStartTime : UFix64
57        access(all) let auctionCompleteTime: UFix64?
58        access(all) let metadata: {String:String}
59        access(all) let nftId: UInt64?
60        access(all) let owner: Address
61        access(all) let leader: BidItem?
62        access(all) let winner: BidWinner?
63        access(all) let minNextBid: UFix64
64        access(all) let sendNftTime: UFix64?
65
66        init(auctionId:UInt64,
67            currentPrice: UFix64,
68            numberOfBids:Int,
69            active: Bool,
70            auctionStatus: AuctionStatusEnum,
71            metadata: {String:String},
72            nftId: UInt64?,
73            leader: BidItem?,
74            winner: BidWinner?,
75            minimumBidIncrement: UFix64,
76            owner: Address,
77            auctionStartTime : UFix64,
78            auctionEndTime : UFix64,
79            auctionCompleteTime: UFix64?,
80            sendNftTime: UFix64?,
81            minNextBid:UFix64,
82        ) {
83            self.auctionId=auctionId
84            self.price= currentPrice
85            self.numberOfBids=numberOfBids
86            self.active=active
87            self.auctionStatus = auctionStatus
88            self.metadata=metadata
89            self.nftId=nftId
90            self.leader= leader
91            self.minimumBidIncrement=minimumBidIncrement
92            self.owner=owner
93            self.auctionStartTime=auctionStartTime
94            self.auctionEndTime=auctionEndTime
95            self.auctionCompleteTime = auctionCompleteTime
96            self.sendNftTime = sendNftTime
97            self.minNextBid=minNextBid
98            self.winner = winner
99        }
100    }
101
102    access(all) enum AuctionStatusEnum: UInt8 {
103        access(all) case auctioning
104        access(all) case compeleted
105        access(all) case winnerReceived
106        access(all) case canceled
107        access(all) case rejected
108    }
109
110    access(all) enum PaymentWithEnum: UInt8 {
111        access(all) case creaditCard
112        access(all) case bankTransfer
113    }
114
115    // The total amount of AuctionItems that have been created
116    access(all) var totalAuctions: UInt64
117
118    // value when settle auction
119    access(all) var defaultRoyalties: MetadataViews.Royalties
120
121    // value when settle auction
122    access(all) let nftRoyalties: {UInt64: MetadataViews.Royalties}
123
124    access(all) let auctionIdRejected: [UInt64]
125    // only MikoSea admin can add user in to black list, {auctionId: [user address]}
126    access(all) let blackList: {UInt64:[Address]}
127
128    access(all) let AdminStoragePath: StoragePath
129    access(all) let CollectionStoragePath: StoragePath
130    access(all) let CollectionPublicPath: PublicPath
131
132    // number of bid on each auction
133    access(all) var bidNumber: {UInt64:UInt64}
134
135    // rate transform from yen to usd, ex: {"USD_TO_YEN": 171.2}
136    access(all) var ratePrice: {String:UFix64}
137
138    // Events
139    access(all) event TokenPurchased(id: UInt64, nftId: UInt64, price: UFix64, from:Address, to:Address?)
140    access(all) event SentNFT(auctionId: UInt64, nftId: UInt64, price: UFix64, to:Address)
141    access(all) event CollectionCreated(owner: Address)
142    access(all) event Created(auctionId: UInt64, owner: Address, startPrice: UFix64, startTime: UFix64, endTime: UFix64, nftId: UInt64, createdAt: UFix64, maxPriceCanPay: UFix64, minimumBidIncrement: UFix64, metadata: {String:String})
143    access(all) event AuctionExtendTime(auctionId: UInt64, newEndTime: UFix64, extendTime: UFix64)
144    access(all) event Bid(auctionId: UInt64, bidderAddress: Address, bidPrice: UFix64, bidTime: UFix64, bidId: UInt64)
145    access(all) event BidderReceipted(auctionId: UInt64, nftId: UInt64, bidder: Address)
146    access(all) event Canceled(auctionId: UInt64)
147    access(all) event Completed(auctionId: UInt64)
148    access(all) event MarketplaceEarned(amount:UFix64, owner: Address)
149    access(all) event AuctionRejected(auctionId: UInt64)
150    access(all) event AuctionUnrejected(auctionId: UInt64)
151    access(all) event AddToBlackList(auctionId: UInt64, addresses: [Address])
152    access(all) event RemoveFromBlackList(auctionId: UInt64, addresses: [Address])
153
154    // AuctionItem contains the Resources and metadata for a single auction
155    access(all) resource AuctionItem {
156        //The id of this individual auction
157        access(contract) let auctionId: UInt64
158
159        access(contract) let bidList: [BidItem]
160
161        //  winner can't payAuction after timeOutWinner (seconds)
162        access(self) var timeOutWinner: UFix64
163        // if winner price is >= maxPriceCanPay; the winner have to bank to ownerAuction and ownerAuction can transfer NFT as manuallly
164        access(self) var maxPriceCanPay: UFix64
165
166        access(self) var paymentWith: PaymentWithEnum
167
168        //The Item that is sold at this auction
169        //It would be really easy to extend this auction with using a NFTCollection here to be able to auction of several NFTs as a single
170        //Lets say if you want to auction of a pack of TopShot moments
171        access(contract) var NFT: @MIKOSEANFT.NFT?
172
173        access(self) var metadata: {String:String}
174
175        //This is the escrow vault that holds the tokens for the current largest bid
176        access(self) let bidVault: @{FungibleToken.Vault}
177
178        //The minimum increment for a bid. This is an english auction style system where bids increase
179        access(self) var minimumBidIncrement: UFix64
180
181        //the time the acution should start at
182        access(self) var auctionStartTime: UFix64
183
184        //the time the acution should end at
185        access(self) var auctionEndTime: UFix64
186
187        //the time the acution should completed at
188        access(self) var auctionCompleteTime: UFix64?
189
190        //the time the winner receive NFT
191        access(self) var sendNftTime: UFix64?
192
193        //Right now the dropitem is not moved from the collection when it ends, it is just marked here that it has ended
194        // auctioning -> completed(when owner compeletes as manually) -> finished(when bidder payAuction)
195        access(self) var auctionStatus: AuctionStatusEnum
196
197        // Auction State
198        access(self) var startPrice: UFix64
199
200        //the capability for the owner of the NFT to return the item to if the auction is cancelled
201        access(self) let ownerCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>
202
203        //the capability to pay the owner of the item when the auction is done
204        access(self) let ownerVaultCap: Capability<&{FungibleToken.Receiver}>
205
206        // access(self) let cutPercentage: UFix64
207        access(self) var royalties: MetadataViews.Royalties
208
209        access(all) let createdAt: UFix64
210
211        init(
212            NFT: @MIKOSEANFT.NFT,
213            minimumBidIncrement: UFix64,
214            auctionStartTime: UFix64,
215            startPrice: UFix64,
216            maxPriceCanPay: UFix64,
217            auctionEndTime: UFix64,
218            timeOutWinner: UFix64,
219            ownerCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>,
220            ownerVaultCap: Capability<&{FungibleToken.Receiver}>,
221            metadata: {String:String},
222            royalties: MetadataViews.Royalties,
223            paymentWith: PaymentWithEnum
224        ) {
225            self.auctionId = MikoSeaNftAuctionV2.totalAuctions
226            self.NFT <- NFT
227            self.minimumBidIncrement = minimumBidIncrement
228            self.auctionEndTime = auctionEndTime
229            self.startPrice = startPrice
230            self.maxPriceCanPay = maxPriceCanPay
231            self.auctionStartTime = auctionStartTime
232            self.auctionStatus = AuctionStatusEnum.auctioning
233            self.ownerCollectionCap = ownerCollectionCap
234            self.ownerVaultCap = ownerVaultCap
235            self.bidList = []
236            self.metadata = metadata
237            self.royalties = royalties
238            self.bidVault <- FlowToken.createEmptyVault(vaultType: Type<@AnyResource>())
239            self.timeOutWinner = timeOutWinner
240            self.createdAt = getCurrentBlock().timestamp
241            self.auctionCompleteTime = nil
242            self.sendNftTime = nil
243            self.paymentWith = paymentWith
244        }
245
246        // get auction metadata
247        access(account) fun getMetadata() : {String:String} {
248            return self.metadata
249        }
250
251        //Number of bids made, that is aggregated to the status struct
252        access(account) fun getNumberOfBids():Int {
253            return self.bidList.length
254        }
255
256        access(account) fun setAuctionStatus(status: AuctionStatusEnum) {
257            self.auctionStatus = status
258        }
259
260        access(account) fun isWinner(bidderAddress: Address) : Bool {
261            return self.getWinner()?.bidItem?.address == bidderAddress
262        }
263
264        access(account) fun getMaxPriceCanPay(): UFix64 {
265            return self.maxPriceCanPay
266        }
267
268        access(account) fun isValidPriceCanPay(_ amount: UFix64):Bool {
269            return amount < self.maxPriceCanPay || self.paymentWith != MikoSeaNftAuctionV2.PaymentWithEnum.creaditCard
270        }
271
272        access(account) fun getCurrentPrice(): UFix64 {
273            let winner = self.getWinner()
274            let leader = self.getLeader()
275            return winner?.bidItem?.bidPrice ?? leader?.bidPrice ?? 0.0
276        }
277
278        access(account) fun completeAuction() {
279            self.setAuctionStatus(status: AuctionStatusEnum.compeleted)
280            self.auctionCompleteTime = getCurrentBlock().timestamp
281        }
282
283        access(all) fun getPymentWith() : MikoSeaNftAuctionV2.PaymentWithEnum{
284            return self.paymentWith
285        }
286
287        access(all) fun floor(_ num: Fix64): Int {
288            var strRes = ""
289            var numStr = num.toString()
290            var i = 0;
291            while i < numStr.length {
292                if numStr[i] == "." {
293                    break;;
294                }
295                strRes = strRes.concat(numStr.slice(from: i, upTo: i))
296                i = i + 1
297            }
298            return Int.fromString(strRes) ?? 0
299        }
300
301        access(all) fun getLeader(): BidItem? {
302            let bidListLen = self.bidList.length
303            if bidListLen == 0 {
304                return nil
305            }
306            if self.isAuctionCompleted() || self.isAuctionFinished() {
307                let winner = self.getWinner()
308                if winner == nil {
309                    return nil
310                }
311                MikoSeaNftAuctionV2.bidNumber[self.auctionId] =  (MikoSeaNftAuctionV2.bidNumber[self.auctionId] ?? 0) + 1
312                return BidItem(
313                    auctionId: self.auctionId,
314                    address: winner!.bidItem.address,
315                    bidPrice: winner!.bidItem.bidPrice,
316                    bidTime: winner!.bidItem.bidTime
317                )
318            }
319            var i = bidListLen - 1
320            while i >= 0 {
321                let bidItem = self.bidList[i]
322                if !MikoSeaNftAuctionV2.isUserInBlacList(auctionId: self.auctionId, address: bidItem.address) {
323                    return bidItem
324                }
325                i = i - 1
326            }
327            return nil
328        }
329
330        access(all) fun getWinnerIndex(): Int? {
331            let bidListLen = self.bidList.length
332            if bidListLen == 0 {
333                return nil
334            }
335            let timeDiff = Fix64(getCurrentBlock().timestamp) - Fix64(self.auctionCompleteTime ?? self.auctionEndTime)
336
337            if timeDiff < 0.0 {
338                return nil
339            }
340
341            let maxTimeOut = Fix64(self.bidList.length) * Fix64(self.timeOutWinner)
342            if timeDiff > maxTimeOut {
343                return nil
344            }
345
346            var step = self.floor(Fix64(timeDiff) / Fix64(self.timeOutWinner))
347            if timeDiff > 0.0 && timeDiff % Fix64(self.timeOutWinner) == 0.0 {
348                step = step - 1
349            }
350            return self.bidList.length - 1 - step
351        }
352
353        access(all) fun getLastTimeCanPay(_ winnerIndex: Int): UFix64 {
354            return (self.auctionCompleteTime ?? self.auctionEndTime) + self.timeOutWinner * UFix64(winnerIndex)
355        }
356
357        access(all) fun getWinner(): BidWinner? {
358            let bidListLen = self.bidList.length
359            if bidListLen == 0 {
360                return nil
361            }
362            if self.isAuctionCanceled() || self.isAuctionRejected() {
363                return nil
364            }
365            let winnerIndex = self.getWinnerIndex()
366            if winnerIndex == nil {
367                return nil
368            }
369            let winner = self.bidList[winnerIndex!]
370            return BidWinner(bidItem: winner, lastTimeCanPay: self.getLastTimeCanPay(winnerIndex!))
371        }
372
373        access(all) fun isCanPlaceBid():Bool {
374            let isRejected = MikoSeaNftAuctionV2.auctionIdRejected.contains(self.auctionId)
375            let active = !isRejected && self.auctionStatus == AuctionStatusEnum.auctioning && self.isAuctionStarted() && !self.isAuctionExpired() && !self.isAuctionFinished()
376            return active;
377        }
378
379        access(self) fun isAuctionCompleted(): Bool {
380            return self.isAuctionExpired() || self.auctionStatus == AuctionStatusEnum.compeleted
381        }
382
383        // is bidder receipted the NFT
384        access(self) fun isAuctionFinished(): Bool {
385            return self.bidList.length > 0 && self.NFT == nil
386        }
387
388        access(self) fun isAuctionStarted(): Bool {
389            return self.auctionStartTime <= getCurrentBlock().timestamp
390        }
391
392        access(self) fun isAuctionCanceled(): Bool {
393            return self.auctionStatus == AuctionStatusEnum.canceled
394        }
395
396        access(self) fun isAuctionRejected(): Bool {
397            return MikoSeaNftAuctionV2.auctionIdRejected.contains(self.auctionId)
398        }
399
400        access(account) fun cancelAuction() {
401            if self.isAuctionCanceled() {
402                return
403            }
404            self.returnAuctionItemToOwner()
405            self.setAuctionStatus(status:  AuctionStatusEnum.canceled)
406            return
407        }
408
409        access(all) fun getCurrentPriceDollar(): UFix64 {
410            return MikoSeaUtility.yenToDollar(yen: self.getCurrentPrice())
411            // return MikoSeaNftAuctionV2.getPriceFromRate(unit: "USD_TO_YEN", price: self.getCurrentPrice())
412        }
413
414        // complete auction as manually
415        access(account) fun sendNftToWiner() {
416            let winer = self.getWinner()
417            if winer == nil {
418                return
419            }
420            let winerCap = getAccount(winer!.bidItem.address).capabilities.get<&{MIKOSEANFT.MikoSeaCollectionPublic}>(MIKOSEANFT.CollectionPublicPath)
421            self.sendNFT(winerCap)
422            self.sendNftTime = getCurrentBlock().timestamp
423        }
424
425        // sendNFT sends the NFT to the Collection belonging to the provided Capability
426        access(contract) fun sendNFT(_ capability: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>) {
427            let nftId = self.NFT?.id ?? panic("NFT not exists")
428            if let collectionRef = capability.borrow() {
429                let nftId = self.NFT?.id ?? panic("NFT_NOT_EXISTS")
430                MIKOSEANFT.removeAllCommentByNftId(nftId)
431
432                let NFT <- self.NFT <- nil
433                collectionRef.deposit(token: <-NFT!)
434
435                emit SentNFT(auctionId: self.auctionId, nftId: nftId, price: self.getCurrentPrice(), to: collectionRef.owner!.address)
436                return
437            }
438            if let ownerCollection=self.ownerCollectionCap.borrow() {
439                let NFT <- self.NFT <- nil
440                ownerCollection.deposit(token: <-NFT!)
441
442                emit SentNFT(auctionId: self.auctionId, nftId: nftId, price: self.getCurrentPrice(), to: ownerCollection.owner!.address)
443                return
444            }
445        }
446
447        // SendNFTs sends the bid tokens to the Vault Receiver belonging to the provided Capability
448        access(contract) fun SendNFTs(_ capability: Capability<&{FungibleToken.Receiver}>) {
449            // borrow a reference to the owner's NFT receiver
450            let bidVaultRef = &self.bidVault as &{FungibleToken.Vault}
451            if let vaultRef = capability.borrow() {
452                vaultRef.deposit(from: <- self.bidVault.withdraw(amount: self.bidVault.balance))
453            } else {
454                let ownerRef= self.ownerVaultCap.borrow()!
455                ownerRef.deposit(from: <- self.bidVault.withdraw(amount: self.bidVault.balance))
456            }
457        }
458
459        //Withdraw cutPercentage to marketplace and put it in their vault
460        access(self) fun depositToCut(cutPercentage: UFix64, receiverCap:Capability<&{FungibleToken.Receiver}>): UFix64 {
461            let receiverRef = receiverCap.borrow()
462            if receiverRef != nil {
463                let amount=self.getCurrentPrice() * cutPercentage
464                let beneficiaryCut <- self.bidVault.withdraw(amount:amount)
465                emit MarketplaceEarned(amount: amount, owner: receiverRef!.owner!.address)
466                receiverRef!.deposit(from: <- beneficiaryCut)
467                return amount
468            }
469            return 0.0
470        }
471
472        // When auction is complete, bidder have to payAuction
473        // bidVault: Dollar
474        access(account) fun payAuction(winner: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>, bidVault: @{FungibleToken.Vault}) {
475            pre {
476                self.NFT != nil: "NFT_NOT_EXISTS"
477            }
478            if self.isValidPriceCanPay(bidVault.balance) {
479                panic("CAN'T_USING_CARD")
480            }
481            if !self.isAuctionFinished() {
482                panic("AUCTION_COMPLETED_OR_EXPIRED")
483            }
484            if !self.isAuctionCanceled() {
485                panic("AUCTION_CANCELED")
486            }
487            if !self.isAuctionRejected() {
488                panic("AUCTION_REJECTED")
489            }
490            if self.isWinner(bidderAddress: winner.address) {
491                panic("NOT_WINNER")
492            }
493            let bidPrice = bidVault.balance
494            let priceNeed = self.getCurrentPrice()
495            let priceNeedDollar = self.getCurrentPriceDollar()
496            if priceNeedDollar > bidPrice {
497                panic("BID_PRICE_MUST_BE_LARGER_".concat(priceNeed.toString()))
498            }
499
500            self.bidVault.deposit(from: <- bidVault)
501            // transfer FT to royalties
502            for royalty in self.royalties.getRoyalties() {
503                let cutValue = self.depositToCut(cutPercentage: royalty.cut, receiverCap: royalty.receiver)
504            }
505
506            // cuts by default
507            for royalty in MikoSeaNftAuctionV2.defaultRoyalties.getRoyalties() {
508                let cutValue = self.depositToCut(cutPercentage: royalty.cut, receiverCap: royalty.receiver)
509            }
510
511            let nftId=self.NFT?.id ?? panic("NFT_NOT_EXISTS")
512
513            self.sendNFT(winner)
514            self.sendNftTime = getCurrentBlock().timestamp
515
516            // cut By Nft
517            for royalty in MikoSeaNftAuctionV2.nftRoyalties[nftId]?.getRoyalties() ?? [] {
518                let cutValue = self.depositToCut(cutPercentage: royalty.cut, receiverCap: royalty.receiver)
519            }
520
521            self.SendNFTs(self.ownerVaultCap)
522
523            // emit TokenPurchased(id: self.auctionId,
524            //     nftId: nftId,
525            //     price: bidPrice,
526            //     from: self.ownerVaultCap.address,
527            //     to: winner.address)
528        }
529
530        access(account) fun returnAuctionItemToOwner() {
531            // deposit the NFT into the owner's collection
532            self.sendNFT(self.ownerCollectionCap)
533         }
534
535        //this can be negative if is expired
536        access(account) fun timeRemaining() : Fix64 {
537            let endTime = self.auctionCompleteTime ?? self.auctionEndTime
538            let currentTime = getCurrentBlock().timestamp
539
540            let remaining= Fix64(endTime) - Fix64(currentTime)
541            return remaining
542        }
543
544        access(account) fun isAuctionExpired(): Bool {
545            let timeRemaining= self.timeRemaining()
546            return timeRemaining < Fix64(0.0)
547        }
548
549        access(account) fun minNextBid() :UFix64{
550            //If there are bids then the next min bid is the current price plus the increment
551            let currentPrice = self.getCurrentPrice()
552            if currentPrice != 0.0 {
553                return currentPrice + self.minimumBidIncrement
554            }
555            //else start price
556            return self.startPrice
557        }
558
559        access(all) fun isValidStepBidPrice(bidPrice: UFix64): Bool {
560            var diffPrice: UFix64 = 0.0
561            if self.getCurrentPrice() == 0.0 {
562                if bidPrice == self.startPrice {
563                    return true
564                }
565                diffPrice = bidPrice - self.startPrice
566            } else {
567                diffPrice = bidPrice - self.getCurrentPrice()
568            }
569            return (diffPrice % self.minimumBidIncrement) == 0.0
570        }
571
572        //Extend an auction with a given set of blocks
573        access(account) fun extendWith(_ amount: UFix64) {
574            pre {
575                self.auctionStatus == AuctionStatusEnum.auctioning: "AUCTION_COMPLETED_OR_EXPIRED"
576            }
577            if !self.isAuctionFinished() {
578                panic("AUCTION_COMPLETED_OR_EXPIRED")
579            }
580            self.auctionEndTime= self.auctionEndTime + amount
581            emit AuctionExtendTime(auctionId: self.auctionId, newEndTime: self.auctionEndTime, extendTime: amount)
582        }
583
584        // This method should probably use preconditions more
585        access(account) fun placeBid(bidPrice: UFix64, bidderCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>) {
586            pre {
587                self.NFT != nil: "NFT_NOT_EXISTS"
588            }
589            if !MikoSeaNftAuctionV2.isUserInBlacList(auctionId: self.auctionId, address: bidderCollectionCap.address) {
590                panic("YOU_ARE_IN_BLACK_LIST")
591            }
592            if !self.isAuctionCanceled() {
593                panic("AUCTION_CANCELED")
594            }
595            if !self.isAuctionRejected() {
596                panic("AUCTION_REJECTED")
597            }
598            if !self.isAuctionCompleted() {
599                panic("AUCTION_COMPLETED_OR_EXPIRED")
600            }
601            if self.isAuctionStarted() {
602                panic("AUCTION_NOT_STARTED")
603            }
604
605            let bidderAddress=bidderCollectionCap.address
606
607            let minNextBid=self.minNextBid()
608            if bidPrice < minNextBid {
609                panic("BID_PRICE_MUST_BE_LARGER_".concat(minNextBid.toString()))
610            }
611            if !self.isValidStepBidPrice(bidPrice: bidPrice) {
612                panic("BID_PRICE_MUSE_BE_STEP_OF_".concat(self.minimumBidIncrement.toString()))
613            }
614
615            // add bidItem in bidList; it also  Update the current price of the token
616            MikoSeaNftAuctionV2.bidNumber[self.auctionId] =  (MikoSeaNftAuctionV2.bidNumber[self.auctionId] ?? 0) + 1
617            let bidItem = BidItem(auctionId: self.auctionId, address: bidderAddress, bidPrice: bidPrice, bidTime: nil)
618            self.bidList.append(bidItem)
619
620            emit Bid(auctionId: self.auctionId, bidderAddress: bidderAddress, bidPrice: bidPrice, bidTime: bidItem.bidTime, bidId: UInt64(bidItem.bidId))
621        }
622
623        access(account) fun getAuctionInfo() :AuctionInfo {
624            let isRejected = MikoSeaNftAuctionV2.auctionIdRejected.contains(self.auctionId)
625            let active = self.isCanPlaceBid()
626            let winner = self.getWinner()
627            let leader = self.getLeader()
628            return AuctionInfo(
629                auctionId:self.auctionId,
630                currentPrice: self.getCurrentPrice(),
631                numberOfBids: self.getNumberOfBids(),
632                active: active,
633                auctionStatus: isRejected ? AuctionStatusEnum.rejected : self.auctionStatus,
634                metadata: self.metadata,
635                nftId: self.NFT?.id,
636                leader: leader,
637                winner: winner,
638                minimumBidIncrement: self.minimumBidIncrement,
639                owner: self.ownerVaultCap.address,
640                auctionStartTime: self.auctionStartTime,
641                auctionEndTime: self.auctionEndTime,
642                auctionCompleteTime: self.auctionCompleteTime,
643                sendNftTime: self.sendNftTime,
644                minNextBid: self.minNextBid(),
645            )
646        }
647
648        // destroy() {
649        //     // send the NFT back to auction owner
650        //     if self.NFT != nil {
651        //         self.sendNFT(self.ownerCollectionCap)
652        //     }
653        //     destroy self.NFT
654        //     destroy self.bidVault
655        // }
656    }
657
658    // AuctionPublic is a resource interface that restricts users to
659    // retreiving the auction price list and placing bids
660    access(all) resource interface AuctionPublic {
661        access(all) fun getAllAuctionInfo(): {UInt64: AuctionInfo}
662        access(all) fun getAuctionInfo(_ id:UInt64): AuctionInfo
663        access(all) fun getBidList(_ id: UInt64): [BidItem]
664        access(all) fun getWinner(_ id: UInt64): BidWinner?
665        access(all) fun getMaxPriceCanPay(id: UInt64): UFix64
666        access(all) fun isValidPriceCanPay(id: UInt64, amount: UFix64): Bool
667        access(all) fun isValidStepBidPrice(auctionId: UInt64, price: UFix64): Bool
668        access(all) fun placeBid(
669            id: UInt64,
670            bidPrice: UFix64,
671            bidderCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>
672        )
673
674        access(all) fun payAcution(
675            id: UInt64,
676            winner: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>,
677            bidVault: @{FungibleToken.Vault}
678        )
679
680        access(all) fun getNftData(_ auctionId: UInt64): MIKOSEANFT.NFTData?
681        access(all) fun getNftImage(_ auctionId: UInt64): String?
682        access(all) fun getNftTitle(_ auctionId: UInt64): String?
683        access(all) fun getNftDescription(_ auctionId: UInt64): String?
684        access(all) fun getMinNextBid(_ auctionId: UInt64): UFix64
685        access(all) fun getAuctionPriceDollar(_ auctionId: UInt64): UFix64
686        access(all) fun getPaymentWith(_ auctionId: UInt64): MikoSeaNftAuctionV2.PaymentWithEnum
687    }
688
689    // AuctionCollection contains a dictionary of AuctionItems and provides
690    // methods for manipulating the AuctionItems
691    access(all) resource AuctionCollection: AuctionPublic {
692        // Auction Items
693        access(all) var auctionItems: @{UInt64: AuctionItem}
694        access(all) let receiverCap: Capability<&{FungibleToken.Receiver}>
695
696        init(
697            receiverCap: Capability<&{FungibleToken.Receiver}>,
698        ) {
699            self.receiverCap = receiverCap
700            self.auctionItems <- {}
701        }
702
703        access(self) fun getAuctionRef(_ id: UInt64): &AuctionItem {
704            pre {
705                self.auctionItems[id] != nil:
706                    "AUCTION_NOT_STARTED"
707            }
708            let itemRef = (&self.auctionItems[id] as &AuctionItem?)!
709            return itemRef
710        }
711
712        // getAuctionPrices returns a dictionary of available NFT IDs with their current price
713        access(all) fun getAllAuctionInfo(): {UInt64: AuctionInfo} {
714            // TODO: implement this method
715            panic("This method is not implemented")
716            // let priceList: {UInt64: AuctionInfo} = {}
717            // for id in self.auctionItems.keys {
718            //     let itemRef = (&self.auctionItems[id] as? &AuctionItem?)!
719            //     priceList[id] = itemRef.getAuctionInfo()
720            // }
721            // return priceList
722        }
723
724        access(all) fun getAuctionInfo(_ id:UInt64): AuctionInfo {
725            let itemRef = self.getAuctionRef(id)
726            return itemRef.getAuctionInfo()
727        }
728
729        access(all) fun getBidList(_ id: UInt64): [BidItem] {
730            // TODO: implement this method
731            panic("This method is not implemented")
732            // let itemRef = self.getAuctionRef(id)
733            // return itemRef.bidList
734            // return []
735        }
736
737        access(all) fun getWinner(_ id: UInt64): BidWinner? {
738            let itemRef = self.getAuctionRef(id)
739            return itemRef.getWinner()
740        }
741
742        access(all) fun getMaxPriceCanPay(id: UInt64): UFix64 {
743            let itemRef = self.getAuctionRef(id)
744            return itemRef.getMaxPriceCanPay()
745        }
746
747        access(all) fun isValidPriceCanPay(id: UInt64, amount: UFix64):Bool {
748            let itemRef = self.getAuctionRef(id)
749            return itemRef.isValidPriceCanPay(amount)
750        }
751
752        access(all) fun isValidStepBidPrice(auctionId: UInt64, price: UFix64): Bool {
753            return self.getAuctionRef(auctionId).isValidStepBidPrice(bidPrice: price)
754        }
755
756        // placeBid sends the bidder's tokens to the bid vault and updates the
757        // currentPrice of the current auction item
758        access(all) fun placeBid(
759            id: UInt64,
760            bidPrice: UFix64,
761            bidderCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>) {
762            // Get the auction item resources
763            let itemRef = self.getAuctionRef(id)
764            itemRef.placeBid(
765              bidPrice: bidPrice,
766              bidderCollectionCap:bidderCollectionCap)
767        }
768
769        access(all) fun payAcution(
770            id: UInt64,
771            winner: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>,
772            bidVault: @{FungibleToken.Vault}
773        ) {
774            let itemRef = self.getAuctionRef(id)
775            itemRef.payAuction(winner: winner, bidVault: <- bidVault)
776        }
777
778        access(all) fun extendAllAuctionsWith(_ amount: UFix64) {
779            // TODO: implement this method
780            panic("This method is not implemented")
781            // for id in self.auctionItems.keys {
782            //     let itemRef = (&self.auctionItems[id] as? &AuctionItem?)!
783            //     itemRef.extendWith(amount)
784            // }
785        }
786
787        access(all) fun extendAuctionWith(id: UInt64, amount: UFix64) {
788            // TODO: implement this method
789            panic("This method is not implemented")
790            // let itemRef = (&self.auctionItems[id] as? &AuctionItem?)
791            // itemRef?.extendWith(amount)
792        }
793
794        access(all) fun keys() : [UInt64] {
795            return self.auctionItems.keys
796        }
797
798        access(all) fun getNftData(_ auctionId: UInt64): MIKOSEANFT.NFTData? {
799            // TODO: implement this method
800            panic("This method is not implemented")
801            // let itemRef = (&self.auctionItems[auctionId] as? &AuctionItem?)!
802            // return itemRef.NFT?.data
803            // return nil
804        }
805
806        access(all) fun getNftImage(_ auctionId: UInt64): String? {
807            // TODO: implement this method
808            panic("This method is not implemented")
809            // let itemRef = (&self.auctionItems[auctionId] as? &AuctionItem?)!
810            // return itemRef.NFT?.getImage()
811            // return nil
812        }
813
814        access(all) fun getNftTitle(_ auctionId: UInt64): String? {
815            // TODO: implement this method
816            panic("This method is not implemented")
817            // let itemRef = (&self.auctionItems[auctionId] as? &AuctionItem?)!
818            // return itemRef.NFT?.getTitle()
819            // return nil
820        }
821
822        access(all) fun getNftDescription(_ auctionId: UInt64): String? {
823            // TODO: implement this method
824            panic("This method is not implemented")
825            // let itemRef = (&self.auctionItems[auctionId] as? &AuctionItem?)!
826            // return itemRef.NFT?.getTitle()
827            // return nil
828        }
829
830        // addTokenToauctionItems adds an NFT to the auction items and sets the meta data
831        // for the auction item
832        access(all) fun createAuction(
833            token: @MIKOSEANFT.NFT,
834            minimumBidIncrement: UFix64,
835            auctionEndTime: UFix64,
836            auctionStartTime: UFix64,
837            timeOutWinner: UFix64,
838            startPrice: UFix64,
839            maxPriceCanPay: UFix64,
840            ownerCollectionCap: Capability<&{MIKOSEANFT.MikoSeaCollectionPublic}>,
841            ownerVaultCap: Capability<&{FungibleToken.Receiver}>,
842            metadata: {String:String},
843            royalties: MetadataViews.Royalties,
844            paymentWith: PaymentWithEnum) {
845
846            pre {
847                ownerCollectionCap.borrow() != nil : "ownerCollectionRef must be required"
848                ownerVaultCap.borrow() != nil : "ownerVaultRef must be required"
849                auctionStartTime < auctionEndTime : "auctionStartTime must be < auctionEndTime"
850            }
851
852            let nftId = token.id
853
854            // create a new auction items resource container
855            let item <- create AuctionItem(
856                NFT: <-token,
857                minimumBidIncrement: minimumBidIncrement,
858                auctionStartTime: auctionStartTime,
859                startPrice: startPrice,
860                maxPriceCanPay: maxPriceCanPay,
861                auctionEndTime: auctionEndTime,
862                timeOutWinner: timeOutWinner,
863                ownerCollectionCap: ownerCollectionCap,
864                ownerVaultCap: ownerVaultCap,
865                metadata: metadata,
866                royalties: royalties,
867                paymentWith: paymentWith
868            )
869            MikoSeaNftAuctionV2.totalAuctions = MikoSeaNftAuctionV2.totalAuctions + 1
870
871            let id = item.auctionId
872            let createdAt = item.createdAt
873
874            // update the auction items dictionary with the new resources
875            let oldItem <- self.auctionItems[id] <- item
876            destroy oldItem
877
878            let owner= ownerVaultCap.address
879
880            MikoSeaNftAuctionV2.bidNumber[id] = 0
881            emit Created(
882                auctionId: id,
883                owner: owner,
884                startPrice: startPrice,
885                startTime: auctionStartTime,
886                endTime: auctionEndTime,
887                nftId: nftId,
888                createdAt: createdAt,
889                maxPriceCanPay: maxPriceCanPay,
890                minimumBidIncrement: minimumBidIncrement,
891                metadata: metadata
892                )
893        }
894
895        access(all) fun cancelAuction(_ id: UInt64) {
896            let itemRef = self.getAuctionRef(id)
897            itemRef.cancelAuction()
898            emit Canceled(auctionId: id)
899        }
900
901        access(all) fun completeAuction(id: UInt64) {
902            let itemRef = self.getAuctionRef(id)
903            itemRef.completeAuction()
904            emit Completed(auctionId: id)
905        }
906
907        access(all) fun sendNftToWiner(_ id: UInt64) {
908            let itemRef = self.getAuctionRef(id)
909            itemRef.sendNftToWiner()
910        }
911
912        access(all) fun getMinNextBid(_ auctionId: UInt64): UFix64 {
913            let itemRef = self.getAuctionRef(auctionId)
914            return itemRef.minNextBid()
915        }
916
917        access(all) fun getAuctionPriceDollar(_ auctionId: UInt64): UFix64 {
918            let itemRef = self.getAuctionRef(auctionId)
919            return itemRef.getCurrentPriceDollar()
920        }
921
922        access(all) fun getPaymentWith(_ auctionId: UInt64): MikoSeaNftAuctionV2.PaymentWithEnum {
923            return self.getAuctionRef(auctionId).getPymentWith()
924        }
925
926        // destroy() {
927        //     // destroy the empty resources
928        //     destroy self.auctionItems
929        // }
930    }
931
932    //------------------------------------------------------------
933    // Admin
934    //------------------------------------------------------------
935
936    access(all) resource Admin {
937        access(all) fun setDefaultAuctionSaleCuts(_ royalty: MetadataViews.Royalties) {
938            MikoSeaNftAuctionV2.defaultRoyalties = royalty
939        }
940
941        access(all) fun setNftAuctionSaleCuts(nftId: UInt64, cuts: MetadataViews.Royalties) {
942            MikoSeaNftAuctionV2.nftRoyalties[nftId] = cuts
943        }
944
945        access(all) fun rejectAuction(auctionId: UInt64) {
946            let indexFound = MikoSeaNftAuctionV2.auctionIdRejected.firstIndex(of: auctionId)
947            if indexFound == nil {
948                MikoSeaNftAuctionV2.auctionIdRejected.append(auctionId)
949            }
950            emit AuctionRejected(auctionId: auctionId)
951        }
952
953        access(all) fun unRejectedAuction(auctionId: UInt64) {
954            let indexFound = MikoSeaNftAuctionV2.auctionIdRejected.firstIndex(of: auctionId)
955            if indexFound != nil {
956                MikoSeaNftAuctionV2.auctionIdRejected.remove(at: indexFound!)
957            }
958            emit AuctionUnrejected(auctionId: auctionId)
959        }
960
961        access(all) fun addUserToBlacklist(auctionId: UInt64, addresses: [Address]) {
962            if MikoSeaNftAuctionV2.blackList[auctionId] == nil {
963                MikoSeaNftAuctionV2.blackList[auctionId] = []
964            }
965            MikoSeaNftAuctionV2.blackList[auctionId]!.appendAll(addresses)
966            emit AddToBlackList(auctionId: auctionId, addresses: addresses)
967        }
968
969        access(all) fun removeUserFromBlacklist(auctionId: UInt64, addresses: [Address]) {
970            if MikoSeaNftAuctionV2.blackList[auctionId] == nil || MikoSeaNftAuctionV2.blackList[auctionId]!.length == 0 {
971                MikoSeaNftAuctionV2.blackList[auctionId] = []
972                return
973            }
974            let temp : [Address] = []
975            for address in MikoSeaNftAuctionV2.blackList[auctionId] ?? [] {
976                if !addresses.contains(address) {
977                    temp.append(address)
978                }
979            }
980            MikoSeaNftAuctionV2.blackList[auctionId] = temp
981            emit RemoveFromBlackList(auctionId: auctionId, addresses: addresses)
982        }
983
984        // access(all) fun setRate(key:String, rate: UFix64) {
985        //     MikoSeaNftAuctionV2.ratePrice[key] = rate
986        // }
987    }
988
989    // MikoSeaAuction public function
990    access(all) fun isUserInBlacList(auctionId: UInt64, address: Address): Bool {
991        return MikoSeaNftAuctionV2.blackList[auctionId]?.contains(address) ?? false
992    }
993
994    // createAuctionCollection returns a new AuctionCollection resource to the caller
995    access(all) fun createAuctionCollection(ownerCap: Capability<&{FungibleToken.Receiver}>): @AuctionCollection {
996        let auctionCollection <- create AuctionCollection(
997            receiverCap: ownerCap
998        )
999
1000        emit CollectionCreated(owner: ownerCap.address)
1001        return <- auctionCollection
1002    }
1003
1004    // access(all) fun getPriceFromRate(unit: String, price: UFix64) : UFix64 {
1005    //     return (MikoSeaNftAuctionV2.ratePrice[unit] ?? 0.0) * price
1006    // }
1007
1008    init() {
1009        let isMainnetAccount = self.account.address == Address(0x0b80e42aaab305f0)
1010        if isMainnetAccount {
1011            self.AdminStoragePath = /storage/MikoSeaNftAuctionV2AdminStoragePath
1012            self.CollectionStoragePath = /storage/MikoSeaNftAuctionV2CollectionStoragePath
1013            self.CollectionPublicPath = /public/MikoSeaNftAuctionV2CollectionPublicPath
1014        } else {
1015            self.AdminStoragePath = /storage/TestMikoSeaNftAuctionV2AdminStoragePath
1016            self.CollectionStoragePath = /storage/TestMikoSeaNftAuctionV2CollectionStoragePath
1017            self.CollectionPublicPath = /public/TestMikoSeaNftAuctionV2CollectionPublicPath
1018        }
1019
1020        self.totalAuctions = 0
1021        self.bidNumber = {}
1022        self.ratePrice = {
1023            "USD_TO_YEN": 137.29
1024        }
1025        self.auctionIdRejected = []
1026        self.defaultRoyalties = MetadataViews.Royalties([])
1027        self.nftRoyalties = {}
1028        self.blackList = {}
1029        // Put the Admin in storage
1030        self.account.storage.save(<- create Admin(), to: self.AdminStoragePath)
1031    }
1032}
1033
1034