Skip to content

Instantly share code, notes, and snippets.

@graemecode
Created March 4, 2021 20:47
Show Gist options
  • Save graemecode/4515b4be741833870cd1f9fc757b5372 to your computer and use it in GitHub Desktop.
Save graemecode/4515b4be741833870cd1f9fc757b5372 to your computer and use it in GitHub Desktop.
//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