Mantle | Mass Adoption of Decentralized and Token-Governed Technologies
What is an Auction?
Auctions have long been recognized as an effective mechanism for buying and selling goods or services. From art auctions to government procurement, this age-old method enables participants to compete openly and determine the highest bidder, ensuring a fair and transparent process. However, with the advent of blockchain technology, the traditional auction landscape is undergoing a transformative shift, introducing unprecedented levels of security, trust, and efficiency. In this article, we explore the concept of auctions and unveil the power of a smart contract designed specifically for conducting auctions on the blockchain.
An auction is a dynamic process where individuals compete to acquire a desired item or service by submitting progressively higher bids. It encompasses an element of excitement, strategy, and competition as participants strive to outbid one another. Historically, auctions have relied on intermediaries and a centralized authority to oversee the process, leading to potential inefficiencies, high transaction costs, and concerns regarding transparency. However, the introduction of smart contracts on the blockchain has revolutionized the auction landscape, offering a decentralized, secure, and automated solution.
The smart contract provided in this article exemplifies the potential of blockchain technology in revolutionizing auctions. Built using Solidity, the contract harnesses the power of the Mantle Network blockchain to create a transparent and tamper-proof environment for conducting auctions. It eliminates the need for intermediaries, reducing costs and enhancing the efficiency of the bidding process. Through the deployment of this smart contract, bidders can directly interact with the auction, placing bids, monitoring the current highest bid, and ultimately determining the winner in a trustless manner.
This smart contract employs the English auction format, where participants bid openly, with the highest bid prevailing. It incorporates essential functionalities such as bid placement, bid validation, and the ability to declare the winner and distribute the funds. Additionally, it enables participants to withdraw their funds if they are outbid, ensuring a seamless and fair experience for all involved parties.
Prerequisites
Before you begin developing the smart contract, please ensure that you have completed this step:
Install both Node.js (>14) and npm on your local machine. To check your Node version, run the following command in your terminal:
node -v
Step 1: Create a Node Project — To create a new node project, navigate to your command line and type the following:
mkdir auction-dapp cd auction-dappnpm init -y
Step 2: Create a Hardhat Project — In your terminal, run the following commands:
npm install -save-dev hardhatnpx hardhat
You should then see a welcome message and options on what you can do. Use your “arrow keys” to navigate the small menu and select “Create a JavaScript Project” by clicking “Enter”.
Step 3: Create Smart Contracts
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Auction { // Structure to represent a bid struct Bid { address bidder; // Address of the bidder uint256 amount; // Bid amount } address public auctioneer; // Address of the auctioneer uint256 public auctionEndTime; // Timestamp when the auction ends bool public ended; // Flag to indicate if the auction has ended address public highestBidder; // Address of the highest bidder uint256 public highestBid; // Highest bid amount mapping(address => uint256) public fundsByBidder; // Funds available to be withdrawn by each bidder mapping(address => Bid) public bidsByBidder; // Bid details for each bidder // Event to be emitted when a new bid is placed event BidPlaced(address indexed bidder, uint256 amount); // Event to be emitted when the auction ends event AuctionEnded(address indexed winner, uint256 amount); // Modifier to check if the auction has ended modifier onlyBeforeEnd() { require(!ended, “Auction already ended.”); require(block.timestamp < auctionEndTime, “Auction already expired.”); _; } // Modifier to check if the auction has not ended modifier onlyAfterEnd() { require(ended, “Auction not yet ended.”); _; } constructor(uint256 _biddingTime) { auctioneer = msg.sender; auctionEndTime = block.timestamp + _biddingTime; } // Function to place a bid function placeBid() public payable onlyBeforeEnd { require(msg.value > 0, “Bid amount must be greater than zero.”); require(msg.value > highestBid, “There is already a higher bid.”); // Return funds to the previous highest bidder if (highestBidder != address(0)) { fundsByBidder[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; bidsByBidder[msg.sender] = Bid(msg.sender, msg.value); emit BidPlaced(msg.sender, msg.value); } // Function to end the auction and declare the winner function endAuction() public onlyAfterEnd { require(!ended, “Auction already ended.”); ended = true; emit AuctionEnded(highestBidder, highestBid); // Transfer the highest bid amount to the auctioneer payable(auctioneer).transfer(highestBid); } // Function to withdraw funds for a bidder function withdraw() public { uint256 amount = fundsByBidder[msg.sender]; require(amount > 0, “No funds available for withdrawal.”); fundsByBidder[msg.sender] = 0; payable(msg.sender).transfer(amount); }}
Let’s understand the workings of our smart contract with a step-by-step explanation:
- The contract starts with a structure called Bid, which represents a bid placed by a bidder. It contains the bidder’s address and the bid amount.
- Next, we declare several state variables including the auctioneer’s address, the auction end time, a flag to indicate if the auction has ended, the address of the highest bidder, and the highest bid amount.
- Two mappings are defined: fundsByBidder to keep track of funds available for withdrawal by each bidder, and bidsByBidder to store the bid details for each bidder.
- Two events are defined: BidPlaced to emit when a new bid is placed, and AuctionEnded to emit when the auction ends.
- Two modifiers are declared. The onlyBeforeEnd modifier checks if the auction has not ended and if the current time is before the auction end time. The onlyAfterEnd modifier checks if the auction has ended.
- The constructor function is defined to initialize the auctioneer and set the auction end time based on the input parameter _biddingTime.
- The placeBid function allows bidders to place a bid by sending ether along with the transaction. It verifies that the bid amount is greater than zero and higher than the current highest bid. If a previous highest bidder exists, their funds are returned. The highest bidder is updated with the new bid information, and the relevant events are emitted.
- The endAuction function is called by the auctioneer to end the auction after the bidding period has expired. It verifies that the auction has not already ended, sets the ended flag, emits the AuctionEnded event, and transfers the highest bid amount to the auctioneer.
- The withdraw function allows bidders to withdraw their funds. It verifies that the bidder has funds available for withdrawal, sets their available funds to zero, and transfers the funds to the bidder.
Step 4: Connect MetaMask & Mantle Network to Your Project
Now that we’ve created a smart contract, it’s time to connect with Mantle Network Testnet.
Every transaction sent from your virtual wallet requires a signature using your unique private key. To provide our program with this permission, we can safely store our private key in an environmental file. This way whenever we push our code to GitHub or any other open source platforms, there wouldn’t be a risk on your accounts by vulnerable private keys.
In order to use the env file, Install the dotenv package in your project directory by running.
npm install dotenv -save
Then, create a .env file in the root directory (Main Folder) of your project, and add your MetaMask private key.
Note: The file name should be “.env” only, not “xyz.env” or “abc.env”
Your .env should look like this:
DEPLOY_ACC_KEY = 0x”your exported private key”
Note: Make sure to replace your exported private key with “your exported private key”.
Step 5: Update hardhat.config.js
We’ve added several dependencies and plugins so far. Now we need to update hardhat.config.js, so that your project knows about all of the configurations to deploy the contract.
Replace the contents of hardhat.config.js with the following:
require(‘@nomiclabs/hardhat-ethers’);module.exports = { networks: { mantle: { url: “https://rpc.testnet.mantle.xyz/”, accounts: [process.env.DEPLOY_ACC_KEY], } }, solidity: “0.8.0”,};
Step 6: Write the Deployment Script
Now that your contract and configuration file is done, it’s time to write the contract deploy script.
Navigate to the scripts/ folder and create a deployment script. To deploy the contract, I created a deployment script called “deploy.js”
deploy.js
async function main() { const Auction = await ethers.getContractFactory(“Auction”); const auction = await Auction.deploy(3600); // Deploying the Auction contract with a bidding time of 1 hour await auction.deployed(); console.log(“Auction contract deployed to:”, auction.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
Compile the smart contract:
- The ethers.getContractFactory function is used to obtain the contract factory object, which represents the Auction contract.
- The auction.deploy(3600) statement deploys an instance of the Auction contract, with 3600 as the bidding time parameter (in this case, representing 1 hour). You can adjust this value as per your requirements.
- The await Auction.deployed() ensures that the deployment process is complete before proceeding.
Logging the deployment details:
- The script logs the deployed contract’s address to the console using console.log(“Auction contract deployed to:”, auction.address)
- Additionally, it saves the deployed contract’s address to a JSON file named address.json within the artifacts directory using the fs.writeFileSync method.
Step 7: Deploy the Contract
Navigate to the terminal and run the following command to deploy the smart contract using Hardhat.
npx hardhat run ./scripts/deployDestination.js -network mantle
Once the contract is successfully compiled and deployed, you should be able to see a similar message in the terminal. It should provide the details like the account that’s used to deploy the contract, and the on-chain smart contract address.
Step 8: Confirm the contract deployment
Now that our contract is deployed, we can go to Mantle Network Explorer and check if our contract was deployed successfully or not. Paste your smart contract address in the search box and you will get the details about the same.
As you can see our contract was successfully deployed as shown below.
Conclusion:
In this tutorial we have created and deployed smart contracts to execute an Auction. Once you have deployed these contracts, you can use this as a foundation to build a dApp with frontend to interact with the smart contract.