A Guide on How to Develop a Dutch Auction Smart Contract

Posted By : Yogesh

Jan 04, 2024

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.

 

Suggested Read | Top 5 Smart Contract Development Companies

 

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.

 

Check It Out | Code Analysis Tools for Solidity Smart Contracts

 

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>

 

Explore More | Best Practices for Smart Contract Development

 

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.

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

April 4, 2024 at 10:29 am

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text