We implement a first-price blind auction where users place their bids to buy an NFT. The winner is the user which places the highest bid. The auction is split in three phases:
- in the bidding phase, users place their sealed bids, and guarantee them by a deposit in ALGO. The deposit must cover the bid amount, and can be redeemed by non-winning users in the second phase;
- in the reveal phase, users reveal their bids. The contract state is updated to keep track of the current winner and highest bid. The non-winners who reveal their bids can redeem their deposits.
- in the redeem phase, the winner can withdraw the NFT and the difference between her deposit and her bid.
The contract state is stored in the following variables:
Global variables:
end_bidding
: last round of the bidding periodend_reveal
: last round of the reveal periodend_redeem
: last round of the redeem periodhighest_bid
: value of the highest bidwinner
: address of the auction winnerNFT
: id of the auctioned token
Local variables:
sealed_bid
: hash of the bid (32 bytes nonce + 2 bytes bid value in ALGO)deposit
: value deposited by the bidder
Any user can create an auction by providing the deadlines. Upon creation, a new token is created. The token will be transferred to the winner in the redeem phase.
@assert end_bidding < end_reveal
@assert end_reveal < end_redeem
@newtok 1 of $NFT -> escrow
Create auction(int end_bidding, int end_reveal, int end_redeem) {
glob.highest_bid = 0
glob.end_bidding = end_bidding
glob.end_reveal = end_reveal
glob.end_redeem = end_redeem
glob.NFT = glob.NFT
}
Users can place a (sealed) bid before the end of the bidding period. This requires to put a deposit to the contract. The deposit can be withdrawn in the redeem phase, provided that the user reveals the bid, and the deposit covers it.
@round (,glob.end_bidding)
@pay $v of ALGO : caller -> escrow
OptIn bid(string sealed_bid) {
loc.sealed_bid = sealed_bid
loc.deposit = v
}
The following two clauses allow users to reveal their bids. A bid consists in a 32-bytes nonce, followed by 2 bytes that encode the bidded amount in ALGOs. A bid can be revealed only if the bid amount is covered by the provided deposit. The highest bid and the corresponding bidder are recorded in the global state.
@round (glob.end_bidding,glob.end_reveal)
@assert sha256(bid) == caller.sealed_bid
@assert len(bid) == 32 + 2
@assert get_int(substring(bid,32,34)) > glob.highest_bid
@assert get_int(substring(bid,32,34)) <= caller.deposit
reveal(string bid) {
glob.highest_bid = get_int(substring(bid,32,34))
glob.winner = caller
}
A non-winner bidder can still reveal, to be able to withdraw her deposit in the redeem phase.
@round (glob.end_bidding,glob.end_reveal)
@assert sha256(bid) == caller.sealed_bid
@assert len(bid) == 32 + 2
@assert get_int(substring(bid,32,34)) <= glob.highest_bid
@assert get_int(substring(bid,32,34)) <= caller.deposit
@pay caller.deposit of ALGO : escrow -> caller
reveal(string bid) {
caller.deposit = 0
}
The winner can redeem the NFT and the difference between the deposit and the bid amount through the following clause:
@round (glob.end_reveal,glob.end_redeem)
@pay caller.deposit - glob.highest_bid of ALGO : escrow -> glob.winner
@pay 1 of glob.NFT : escrow -> glob.winner
redeem() {
caller.deposit = 0
}
The following clause allows the creator to delete a terminated auction, and to redeem all the assets contained therein:
@round (glob.end_redeem,)
@from creator
@close glob.NFT : escrow -> creator
@close ALGO : escrow -> creator
Delete delete() {}
The project is not audited and should not be used in a production environment.