auction.gno
4.04 Kb · 127 lines
1package gnogle_market2
2
3import (
4 "chain"
5 "strconv"
6 "time"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/ufmt/v0"
10 nft "gno.land/r/g18wk4a80cr7dqa25vfka2yug5n3pd50udled6y3/gnogle_nft2"
11)
12
13// Auction is a timed English auction. The NFT is NOT escrowed (it stays with the
14// seller); only the highest bid funds are held by the market.
15type Auction struct {
16 collID string
17 tokenID string
18 seller address
19 minBid int64
20 highestBid int64
21 highestBidder address
22 endTime time.Time
23 settled bool
24}
25
26var auctions avl.Tree // "collID/tokenID" -> *Auction
27
28func getAuction(collID, tokenID string) (*Auction, bool) {
29 v, ok := auctions.Get(key(collID, tokenID))
30 if !ok {
31 return nil, false
32 }
33 return v.(*Auction), true
34}
35
36func mustGetAuction(collID, tokenID string) *Auction {
37 a, ok := getAuction(collID, tokenID)
38 if !ok {
39 panic("no active auction for this token")
40 }
41 return a
42}
43
44// CreateAuction opens an auction for a token the caller owns (and has approved
45// this market for).
46func CreateAuction(cur realm, collID, tokenID string, minBid, durationSecs int64) {
47 assertUserCall(cur)
48 if minBid < 0 {
49 panic("minBid cannot be negative")
50 }
51 if durationSecs <= 0 {
52 panic("duration must be positive")
53 }
54 seller := cur.Previous().Address()
55 requireOwnerAndApproval(cur, collID, tokenID, seller)
56 if _, ok := getAuction(collID, tokenID); ok {
57 panic("token is already in an auction")
58 }
59 if _, ok := getListing(collID, tokenID); ok {
60 panic("token is listed for sale; cancel that listing first")
61 }
62 auctions.Set(key(collID, tokenID), &Auction{
63 collID: collID, tokenID: tokenID, seller: seller, minBid: minBid,
64 endTime: time.Now().Add(time.Duration(durationSecs) * time.Second),
65 })
66 chain.Emit("AuctionCreated", "collection", collID, "tokenId", tokenID, "seller", seller.String(), "minBid", strconv.FormatInt(minBid, 10))
67}
68
69// Bid places a bid; attach GNOT greater than the current highest. The previous
70// highest bidder is refunded.
71func Bid(cur realm, collID, tokenID string) {
72 assertUserCall(cur)
73 a := mustGetAuction(collID, tokenID)
74 bidder := cur.Previous().Address()
75 if a.settled {
76 panic("auction already settled")
77 }
78 if !time.Now().Before(a.endTime) {
79 panic("auction has ended")
80 }
81 if bidder == a.seller {
82 panic("seller cannot bid on their own auction")
83 }
84 amount := receivedUgnot()
85 if amount < a.minBid {
86 panic(ufmt.Sprintf("bid must be at least %d ugnot", a.minBid))
87 }
88 if amount <= a.highestBid {
89 panic(ufmt.Sprintf("bid must exceed the current highest bid of %d ugnot", a.highestBid))
90 }
91 if a.highestBid > 0 && a.highestBidder != zeroAddr {
92 payout(cur, a.highestBidder, a.highestBid)
93 }
94 a.highestBid = amount
95 a.highestBidder = bidder
96 chain.Emit("Bid", "collection", collID, "tokenId", tokenID, "bidder", bidder.String(), "amount", strconv.FormatInt(amount, 10))
97}
98
99// EndAuction settles an auction after its end time. With a winning bid the NFT
100// goes to the winner (if the seller still owns it and the market is still
101// approved); otherwise the winner is refunded.
102func EndAuction(cur realm, collID, tokenID string) {
103 assertUserCall(cur)
104 a := mustGetAuction(collID, tokenID)
105 if a.settled {
106 panic("auction already settled")
107 }
108 if time.Now().Before(a.endTime) {
109 panic("auction has not ended yet")
110 }
111 a.settled = true
112 auctions.Remove(key(collID, tokenID))
113
114 if a.highestBid == 0 {
115 chain.Emit("AuctionEnded", "collection", collID, "tokenId", tokenID, "winner", "", "amount", "0")
116 return
117 }
118 // Seller must still own the token and keep the market approved; else refund.
119 if nft.OwnerOf(collID, tokenID) != a.seller || !marketApproved(cur, collID, tokenID, a.seller) {
120 payout(cur, a.highestBidder, a.highestBid)
121 chain.Emit("AuctionVoided", "collection", collID, "tokenId", tokenID, "refunded", a.highestBidder.String())
122 return
123 }
124 nft.TransferFrom(cross(cur), collID, a.seller, a.highestBidder, tokenID)
125 settle(cur, collID, tokenID, a.seller, a.highestBidder, a.highestBid, "auction")
126 chain.Emit("AuctionEnded", "collection", collID, "tokenId", tokenID, "winner", a.highestBidder.String(), "amount", strconv.FormatInt(a.highestBid, 10))
127}