Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}