Sitemap

Building an NFT Auction Smart Contract with Solidity and OpenZeppelin

4 min readJul 4, 2025

--

Hey devs! 👋

Today I want to share a simple but powerful Solidity project I built: a decentralized NFT auction using the ERC-721 standard, fully open source on GitHub.

The idea was to allow people to mint and auction unique digital assets (NFTs) on-chain, handling the auction lifecycle securely and transparently. I chose ERC-721 since it’s the de facto standard for non-fungible tokens, unlike ERC-20, which handles interchangeable, fungible tokens (like standard currencies).

Here, I’ll break down what I implemented, why, and what challenges I faced.

What Did I Build?

The contract allows the owner to start an auction for an NFT (identified by its tokenId), setting a minimum price and duration. Bidders can place bids with ETH, and the highest bid wins once the auction timer expires. The winner gets minted the NFT, and the owner receives the winning bid.

Key features include:

  • Time-limited auctions: Each auction lasts a predefined period.
  • Secure bidding with refund withdrawal: Losing bidders can withdraw their bids safely, avoiding common pitfalls like reentrancy attacks.
  • Minting NFTs on auction finalization: The NFT is minted and transferred to the winner at the end.
  • Built on OpenZeppelin: I used OpenZeppelin’s battle-tested contracts for ERC-721 and ownership management to ensure security and save time.

Minting the NFT

Only after the auction ends and is finalized does the contract mint the NFT to the highest bidder. This ensures the NFT only exists once it has a legitimate owner from the auction process. I used OpenZeppelin’s ERC721URIStorage to handle the token metadata URI properly.

🌟 Why ERC-721?

ERC-721 is designed for unique assets. Each token has its own tokenId and metadata, making it ideal for art, collectibles, and even real-world digital twins.

That’s very different from ERC-20, where all tokens are interchangeable and indistinguishable from each other (like dollars or stablecoins).

So, whenever you want to represent uniqueness on-chain, ERC-721 is the way to go.

🔨 How the Auction Contract Works

The smart contract, written in Solidity, uses OpenZeppelin’s awesome library for secure, battle-tested building blocks.

Here’s what’s under the hood:

✅ Extends ERC721URIStorage so we can store metadata (images, descriptions) on-chain or IPFS
✅ Uses Ownable to manage ownership and restrict certain functions to the contract deployer
✅ Defines a struct called AuctionItem to track:

  • token ID
  • minimum price
  • highest bid
  • highest bidder
  • auction end time
  • whether the auction ended

✅ Stores all auctions in a mapping(uint256 => AuctionItem)
✅ Uses another mapping, pendingReturns, to securely let losing bidders withdraw their funds (instead of sending funds directly and risking reentrancy issues). This is known as the pull-over-push pattern, a common best practice in Solidity.

Withdrawing Funds Safely

One tricky part of writing auctions in Solidity is returning funds to people who get outbid. If you try to immediately refund the previous bidder in the middle of the bid() function, you expose yourself to reentrancy attacks (the infamous DAO hack style).

Get Ikaro Sales’s stories in your inbox

Join Medium for free to get updates from this writer.

To avoid that, I used a pull-over-push pattern:

Instead of sending Ether directly, I store it in a pendingReturns mapping, and let the user manually call withdraw() to get their money back.

This is widely considered the safest approach. Shout out to OpenZeppelin for encouraging this best practice in their documentation.

Gas Costs

Let’s talk gas for a second. Auctions naturally involve many storage writes:

  • bids update the auction state
  • people claiming funds
  • minting NFTs to winners

So you can expect higher-than-average gas consumption, especially on finalize. That’s something to keep in mind if you want to run many parallel auctions.

🚀 Features

  • Start auction: the owner launches an auction with a minimum price, a duration, and token metadata.
  • Bid: anyone can place a bid higher than the current highest, as long as the auction is open.
  • Finalize auction: once the timer expires, the highest bidder wins, and the NFT is minted to them.
  • Withdraw: outbid bidders can reclaim their ETH safely.

This means everything happens transparently on-chain, no trust needed.

⚠️ Challenges

While building, a few classic Solidity pain points popped up:

👉 Handling floats: ETH is handled in wei (integers only). There’s no such thing as “0.05” in Solidity — you must always work with integer values.
👉 Reentrancy: refunding previous bids inline (with .transfer) is risky. Using pendingReturns with a manual withdraw() prevents malicious contracts from reentering the contract and draining funds.
👉 Auction timing: Solidity has no cron jobs. You rely on block.timestamp and let users trigger functions like finalizeAuction() when the auction period expires.

🛠️ Why OpenZeppelin

Honestly, writing ERC-721 and ownership code from scratch is a huge security risk. That’s why I imported OpenZeppelin’s:

  • ERC721
  • ERC721URIStorage
  • Ownable

These contracts are audited, widely used, and trusted by the community. That means less worry about bugs and more focus on auction logic.

🤝 Contributing

If you’d like to check it out or even contribute, the repo is open:
👉 IkaroSantos/auction-smart-contract on GitHub

Feel free to fork it, suggest pull requests, or open issues.

🚀 Final Thoughts

Writing secure smart contracts is not easy, but with the right patterns (like pull-over-push), solid libraries (like OpenZeppelin), and a good understanding of standards (ERC-721 vs ERC-20), you can build powerful and safe dApps for the next generation of Web3.

If you’re curious to extend this, you could:

  • Add events (for more off-chain indexing)
  • Use Chainlink price feeds for dynamic pricing
  • Integrate royalties for creators
  • Add auction extensions like time buffers or reserve prices

Let’s keep learning and building together. See you on-chain!

👉 Questions or ideas? Drop them in the comments!

Happy coding! 🤘

--

--

Ikaro Sales
Ikaro Sales

Written by Ikaro Sales

Hi, I'm Ikaro Sales. Working with Development for 6 years. Lover of Development, Blockchain, Java, Node.js, Web3. https://linkedin.com/in/IkaroSales