Created
March 4, 2021 20:47
-
-
Save graemecode/4515b4be741833870cd1f9fc757b5372 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: GPL-3.0-or-later | |
pragma solidity 0.6.8; | |
pragma experimental ABIEncoderV2; | |
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
/** | |
* @title ZoraColdieAuctionReference | |
* @author Graeme (@strangechances) from MirrorXYZ | |
* | |
* An example reference for building a Coldie auction on top of Zora, to give some | |
* ideas for whomever wants to implement it. | |
* | |
* NB: Not meant to be an actual implementation, and probably includes _many_ fatal bugs | |
* that will result in loss of funds! | |
*/ | |
contract ZoraColdieAuctionReference { | |
// ============ Constants ============ | |
// Auction is 24 hours by default. | |
uint32 public constant defaultAuctionLength = 86400; | |
// Auction extended 10 minute after each bid. | |
uint16 public constant auctionTimeIncrement = 600; | |
// ============ Immutable Storage ============ | |
// Zora media -- define in constructor for dependency injection. | |
address public immutable media; | |
// Zora market -- define in constructor for dependency injection. | |
address public immutable market; | |
// ============ Mutable Storage ============ | |
mapping(uint256 => Auction) public auctions; | |
mapping(uint256 => Bid) public currentBids; | |
// ============ Structs ============ | |
struct Bid { | |
// Bidder must be payable, so that we can refund if necessary. | |
address payable bidder; | |
uint256 amount; | |
} | |
struct Auction { | |
// Keep track of creator in case we return bid. Payable so that we can pay them! | |
address payable creator; | |
// Zora market tokenId for the NFT | |
uint256 tokenId; | |
// Allow the creator to set a reserve price, below which | |
// the auction will not settle and the NFT is returned. | |
uint256 reservePrice; | |
// How long should the auction continue for, since the first bid? | |
uint256 duration; | |
// Unix timestamp of the first bid | |
uint256 firstBidTime; | |
} | |
// ============ Constructor ============ | |
constructor(address media_, address market_) public { | |
media = media_; | |
market = market_; | |
} | |
// ============ Auction Methods ============ | |
function createAuction( | |
uint256 tokenId, | |
uint256 reservePrice, | |
address payable creator | |
) external { | |
// Initialize the auction, with a "null" starting time (this gets initialized on first bid). | |
auctions[tokenId] = Auction( | |
creator, | |
tokenId, | |
reservePrice, | |
defaultAuctionLength, | |
0 | |
); | |
// We bring the NFT into escrow before starting the auction, so that we can send it | |
// later to the auction winner. | |
ERC721(tokenId).transferFrom(creator, address(this), tokenId); | |
} | |
function endAuction(uint256 tokenId) external { | |
Auction memory auction = auctions[tokenId]; | |
Bid memory currentBid = currentBids[tokenId]; | |
// Ensure the time has run out. | |
uint256 auctionEndTime = auction.firstBidTime + auction.duration; | |
bool auctionFinished = block.timestamp >= auctionEndTime; | |
require( | |
auctionFinished, | |
"ZoraColdieAuction: The auction is not over yet!" | |
); | |
address highestBidder = currentBid.bidder; | |
// If the bidder is the null address, then no bids were successful. | |
bool noBidder = highestBidder == address(0); | |
if (noBidder) { | |
// Transfer back to creator, since the auction is over and there was no bidder! | |
ERC721(media).transferFrom(address(this), auction.creator, tokenId); | |
} else { | |
// Send the NFT to the highest bidder! | |
ERC721(media).transferFrom( | |
address(this), | |
currentBid.bidder, | |
tokenId | |
); | |
// Here we might want to respect Zora's BidShares data from the market, | |
// and transfer payment in ETH to the respective people according to that. | |
// At the minimum, the creator should receive ETH from this contract. | |
address payable creator = auction.creator; | |
uint256 amount = currentBid.amount; | |
creator.transfer(amount); | |
} | |
} | |
// ============ Bids ============ | |
function createBid(uint256 tokenId) external payable { | |
// The amount gets sent in as ETH in the transaction. | |
uint256 amount = msg.value; | |
Auction memory auction = auctions[tokenId]; | |
Bid memory currentBid = currentBids[tokenId]; | |
uint256 currentBidAmount = currentBid.amount; | |
uint256 reservePrice = auction.reservePrice; | |
require( | |
amount >= reservePrice, | |
"ZoraColdieAuction: Bid does not meet reserve price!" | |
); | |
require( | |
amount > currentBidAmount, | |
"ZoraColdieAuction: Bid is lower than current highest bid!" | |
); | |
address payable currentBidder = currentBid.bidder; | |
if (currentBidder == address(0)) { | |
// This is the first bid! Set the timestamp. | |
auction.firstBidTime = block.timestamp; | |
} else { | |
// Refund the current bid. | |
currentBidder.transfer(amount); | |
// Increment the duration by the increment amount. | |
auction.duration += auctionTimeIncrement; | |
} | |
currentBids[tokenId] = Bid(msg.sender, amount); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment