Smart Contract
MikoSeaNftAuctionV2
A.0b80e42aaab305f0.MikoSeaNftAuctionV2
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