A Guide on How to Develop a Dutch Auction Smart Contract

A Guide on How to Develop a Dutch Auction Smart Contract
8 min read

This comprehensive guide takes you through the process of creating a Dutch Auction smart contract using Solidity. From explaining the concept of Dutch auctions and prerequisites to Hardhat project setup, this blog gives you insights into the entire smart contract development process. 

Understanding a Dutch Auction

Auctions function as public selling platforms, employing a bidding process where the highest bidder typically emerges as the winner, securing ownership of the item. A Dutch auction is like a special sale where the price of something starts high and gradually goes down. It's commonly used for things like flowers or food that can't last long. 

People can bid on the item, and whoever bids the most when the price is right wins. The tricky part is that the longer you wait, the cheaper it gets, but someone else might grab it first if you wait too long. So, it's a bit of a game to get a good deal without waiting too much.

Prerequisites for Dutch Auction Smart Contract Development

  • A MetaMask wallet
  • ETH on Sepolia testnet
  • Understanding of creating an ERC721 (NFT) smart contract

Multiple Step Explanation

Setup Hardhat Project

  • Step 1: Create a New Project Directory

Open your terminal and create a new directory for your Hardhat project:

mkdir your-project-name

cd your-project-name

  • Step 2: Initialize the Hardhat Project

Run the following command to initialize your project with Hardhat:

npx hardhat init

This command will set up the basic structure and configuration files for your Ethereum project.

  • Step 3: Project Structure

Your project structure will look something like this:

your-project-name/

|-- contracts/

|-- scripts/

|-- test/

|-- hardhat.config.js

|-- .gitignore

|-- artifacts/

|-- cache/

|-- node_modules/

|-- README.md

- contracts: This is where your Solidity smart contracts will reside.

- scripts: You can place your deployment scripts here.

- test: Write your tests for smart contracts here.

- hardhat.config.js: Configuration file for Hardhat.

- .gitignore: Gitignore file to exclude unnecessary files from version control.

- artifacts, cache, node_modules: Directories generated by Hardhat.

  • Step 4: Customize Configuration (Optional)

Open hardhat.config.js to customize your configuration. You can set up networks, add plugins, and define other settings based on your project requirements.

Create a Dutch Auction Smart Contract

We need to first create and deploy an NFT (non-fungible token) contract and mint the NFT. Create two solidity files, NFT.sol and dutchAuction.sol, in the contracts folder.

NFT.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

// Import necessary OpenZeppelin contracts

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/access/Ownable.sol";

// OodlesNFT contract inherits from ERC721 and Ownable

contract OodlesNFT is ERC721, Ownable {

    // Constructor to initialize the contract with the specified initial owner

    constructor(address initialOwner)

        ERC721("OodlesNFT", "ONFT")

        Ownable(initialOwner)

    {}

    // Function to safely mint a new token

    // Only the owner is allowed to call this function

    function safeMint(address to, uint256 tokenId) public onlyOwner {

        _safeMint(to, tokenId);

    }

}

This Solidity smart contract defines an ERC-721-compliant NFT named "OodlesNFT." The contract is created to represent unique digital assets on the blockchain. The SPDX-License-Identifier indicates that the contract is released under the MIT license. The contract inherits functionality from two OpenZeppelin contracts: ERC721, which establishes the basic structure for an ERC-721 token, and Ownable, which ensures that only the designated owner can execute certain functions.

The contract has a constructor that initializes the ERC721 token with the name "OodlesNFT" and the symbol "ONFT." The Ownable constructor sets the initial owner of the contract, specified during deployment.

The primary function of the contract is safeMint, which allows the owner to securely create and assign a new token to a specified address. This function is restricted to the owner only, ensuring that only the designated owner has the authority to mint new tokens. 

The minting process adheres to the ERC-721 standard, providing a safe and standardized way to create unique tokens on the Ethereum blockchain. Overall, this contract forms the foundation for managing and transferring ownership of NFTs within the OodlesNFT collection.

DutchAuction.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

// Interface for ERC-721 token

interface IERC721 {

    function transferFrom(

        address fromAddress,

        address toAddress,

        uint tokenId

    ) external;

}

contract DutchAuction is Ownable {

    // Duration of the Dutch auction

    uint private constant AUCTION_DURATION = 15 minutes;

    // Immutable state variables

    IERC721 public immutable nftContract;

    uint public immutable tokenId;

    address payable public immutable sellerAddress;

    uint public immutable initialPrice;

    uint public immutable priceDiscountRate;

    uint public immutable auctionStartTimestamp;

    uint public immutable auctionEndTimestamp;

    address public _owner;

    // Constructor initializes the Dutch auction parameters

    constructor(

        uint _initialPrice,

        uint _priceDiscountRate,

        address _nftContract,

        uint _tokenId,

        address initialOwner

    ) Ownable(initialOwner) {

        // Set the auction parameters

        sellerAddress = payable(msg.sender);

        initialPrice = _initialPrice;

        priceDiscountRate = _priceDiscountRate;

        auctionStartTimestamp = block.timestamp;

        auctionEndTimestamp = block.timestamp + AUCTION_DURATION;

        // Ensure initial price is feasible based on discount rate and duration

        require(_initialPrice >= _priceDiscountRate * AUCTION_DURATION, "Initial price is too low");

        // Initialize ERC-721 token interface

        nftContract = IERC721(_nftContract);

        tokenId = _tokenId;

        _owner = initialOwner;

    }

    // Function to calculate the current price of the token in the auction

    function getCurrentPrice() public view returns (uint) {

        uint timeElapsed = block.timestamp - auctionStartTimestamp;

        uint discount = priceDiscountRate * timeElapsed;

        return initialPrice - discount;

    }

    // Function for buyers to participate in the auction

    function participateInAuction() external payable {

        // Ensure the auction is still active

        require(block.timestamp < auctionEndTimestamp, "This auction has ended");

        // Get the current price of the token

        uint currentPrice = getCurrentPrice();

        // Ensure the sent ETH is sufficient to cover the token price

        require(msg.value >= currentPrice, "Sent ETH is less than the price of the token");

        // Transfer the token from the seller to the buyer

        nftContract.transferFrom(sellerAddress, msg.sender, tokenId);

        // Calculate and refund any excess ETH sent by the buyer

        uint refundAmount = msg.value - currentPrice;

        if (refundAmount > 0) {

            payable(msg.sender).transfer(refundAmount);

        }

    }

    /**

     * @dev Allows the owner to withdraw both ETH and ERC-20 tokens.

     * @param _to The destination address for the withdrawal.

     * @param _ethAmount The amount of ETH to withdraw.

     */

    function withdraw(

        address payable _to,

        uint256 _ethAmount

    ) external onlyOwner {

        require(address(_to) != address(0), "Invalid address");

        if (_ethAmount > 0 && address(this).balance >= _ethAmount) {

            _to.transfer(_ethAmount);

        }

    }

}

This Solidity smart contract represents a Dutch auction for an ERC-721 non-fungible token (NFT). A Dutch auction is a type of auction where the price of the item is gradually reduced until a buyer decides to purchase at the current price. Here's an explanation of the main functionalities:

The contract begins with defining a duration for the Dutch auction, set to 15 minutes. It then imports the Ownable contract from OpenZeppelin to manage ownership.

The DutchAuction contract itself takes several parameters during deployment, including the initial auction price, price discount rate per unit of time, the address of the ERC-721 NFT contract, the token ID, and the initial owner's address. The constructor sets up various immutable state variables, such as auction start and end timestamps, the seller's address, and the initial and discount prices.

The getCurrentPrice function calculates the current price of the NFT in the auction based on the elapsed time and the discount rate. The participateInAuction function allows buyers to participate in the auction by sending ETH. It checks if the auction is still active, calculates the current price, ensures the sent ETH is sufficient, transfers the NFT from the seller to the buyer, and refunds any excess ETH.

Lastly, the withdraw function allows the owner to withdraw ETH from the contract. It ensures a valid withdrawal address and checks the contract's balance before transferring the specified amount of ETH.

deploy.js

const hre = require("hardhat");

async function main() {

  const nft = await hre.ethers.deployContract("OodlesNFT", [

    "0x06C2479D95AEe2C66e3369440A92EC0AA2885Ea0",

  ]);

  await nft.waitForDeployment();

  console.log(`NFT ${nft.target}`);

  const dutchSmartContract = await hre.ethers.deployContract("Dutch", [

    "1000000000000000000",

    "1",

    nft.target,

    "1",

    "0x06C2479D95AEe2C66e3369440A92EC0AA2885Ea0"

  ]);

  await dutchSmartContract.waitForDeployment();

  console.log(`dutchSmartContract ${dutchSmartContract.target}`);

}

main().catch((error) => {

  console.error(error);

  process.exitCode = 1;

});

Change the configuration of hardhat.config.js 

require("@nomicfoundation/hardhat-toolbox");

require("@openzeppelin/hardhat-upgrades");

/** @type import('hardhat/config').HardhatUserConfig */

module.exports = {

  solidity: {

    compilers: [

      {

        version: "0.8.20",

        settings: {

          optimizer: {

            enabled: true,

            runs: 200,

          },

        },

      },

    ],

  },

  sourcify: {

    enabled: true

  },  

  networks: {

    sepolia: {

      url: `<RPC_URL>`,

      accounts: ["<PRIVATE_KEY>"],

    },

  },

  etherscan: {

    apiKey: "<NETWORK_API_KEY>",

  },

};

RUN:

deploy - npx hardhat run scripts/deploy.js --network <network> 

verify - npx hardhat verify <Contract Address> --network <network> <constructor arguments>

Conclusion

If you've reached this point, well done! You're progressing toward solidifying your expertise in Solidity. Throughout this guide, we delved into the intricacies of smart contract auctions, exploring the process of crafting and deploying a Solidity smart contract specifically designed for Dutch auctions. 

If you are interested in smart contract development, then connect with our smart contract developers to get started.

In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.
Arslan Siddiqui 2
Joined: 5 months ago
Comments (0)

    No comments yet

You must be logged in to comment.

Sign In / Sign Up