Smart Contract for NFT Auctions Solidity, JavaScript
👤 Sharing: AI
Okay, here's a basic implementation of an NFT auction smart contract in Solidity, along with accompanying JavaScript code to interact with it. This is a simplified example and doesn't include features like fee structures, gas optimization, or advanced security measures. Remember to test thoroughly before deploying to a live network.
**Solidity (Auction.sol):**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Auction is ReentrancyGuard {
IERC721 public nftContract;
uint256 public tokenId;
address payable public seller;
uint256 public auctionEndTime;
address public highestBidder;
uint256 public highestBid;
bool public ended;
event AuctionStarted(address indexed seller, uint256 tokenId, uint256 endTime);
event BidPlaced(address indexed bidder, uint256 amount);
event AuctionEnded(address winner, uint256 amount);
event AuctionCancelled();
constructor(address _nftContract, uint256 _tokenId, uint256 _auctionDuration) {
nftContract = IERC721(_nftContract);
tokenId = _tokenId;
seller = payable(msg.sender); // Make the seller address payable
auctionEndTime = block.timestamp + _auctionDuration;
ended = false;
// Transfer the NFT to the contract upon creation
require(nftContract.ownerOf(tokenId) == msg.sender, "You are not the owner of the NFT.");
nftContract.transferFrom(msg.sender, address(this), tokenId);
emit AuctionStarted(msg.sender, tokenId, auctionEndTime);
}
modifier notEnded() {
require(!ended, "Auction has already ended.");
_;
}
modifier validBid(uint256 amount) {
require(block.timestamp < auctionEndTime, "Auction has ended.");
require(amount > highestBid, "Bid not high enough.");
_;
}
function bid() external payable notEnded validBid(msg.value) nonReentrant {
if (highestBidder != address(0)) {
// Refund the previous highest bidder
payable(highestBidder).transfer(highestBid); //Explicitly make bidder payable
}
highestBidder = msg.sender;
highestBid = msg.value;
emit BidPlaced(msg.sender, msg.value);
}
function endAuction() external notEnded {
require(block.timestamp >= auctionEndTime, "Auction is not over yet.");
ended = true;
if (highestBidder != address(0)) {
// Transfer NFT to the highest bidder
nftContract.transferFrom(address(this), highestBidder, tokenId);
// Pay the seller
seller.transfer(highestBid);
emit AuctionEnded(highestBidder, highestBid);
} else {
//No bids, return NFT to seller
nftContract.transferFrom(address(this), seller, tokenId);
}
}
function cancelAuction() external {
require(msg.sender == seller, "Only the seller can cancel the auction.");
require(!ended, "Auction has already ended.");
ended = true;
nftContract.transferFrom(address(this), seller, tokenId);
emit AuctionCancelled();
}
// Fallback function to receive Ether
receive() external payable {}
}
```
**Explanation (Solidity):**
* **`SPDX-License-Identifier: MIT`**: Standard license identifier.
* **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version. Use a recent version (>=0.8.0) for security and features.
* **`import "@openzeppelin/contracts/token/ERC721/IERC721.sol";`**: Imports the IERC721 interface from OpenZeppelin. This allows the contract to interact with ERC721 (NFT) contracts. You'll need to install the OpenZeppelin contracts package (see setup instructions below).
* **`import "@openzeppelin/contracts/security/ReentrancyGuard.sol";`**: Imports OpenZeppelin's ReentrancyGuard to prevent reentrancy attacks.
* **`contract Auction is ReentrancyGuard`**: Defines the `Auction` contract, inheriting from `ReentrancyGuard`.
* **State Variables:**
* `nftContract`: The address of the ERC721 contract.
* `tokenId`: The ID of the NFT being auctioned.
* `seller`: The address of the seller of the NFT. Declared as `payable` so the contract can send Ether to it.
* `auctionEndTime`: A Unix timestamp representing when the auction ends.
* `highestBidder`: The address of the current highest bidder.
* `highestBid`: The current highest bid amount (in Wei).
* `ended`: A boolean indicating whether the auction has ended.
* **Events:** `AuctionStarted`, `BidPlaced`, `AuctionEnded`, `AuctionCancelled`. These are emitted to log important actions, which can be monitored by user interfaces or other contracts.
* **`constructor(address _nftContract, uint256 _tokenId, uint256 _auctionDuration)`:** The constructor function, executed when the contract is deployed.
* Takes the NFT contract address, token ID, and auction duration (in seconds) as arguments.
* Sets the state variables.
* Transfers the NFT from the seller to the contract itself.
* Emits the `AuctionStarted` event.
* Requires that the sender is the owner of the NFT.
* **Modifiers:**
* `notEnded()`: Checks if the auction has already ended.
* `validBid(uint256 amount)`: Checks if the bid is higher than the current highest bid and if the auction is still running.
* **`bid()` Function:**
* Allows users to place bids.
* Uses the `notEnded` and `validBid` modifiers.
* If there's a previous highest bidder, refunds their bid.
* Sets the new highest bidder and bid amount.
* Emits the `BidPlaced` event.
* Uses `nonReentrant` modifier to prevent reentrancy attacks.
* **`endAuction()` Function:**
* Allows anyone to end the auction after the `auctionEndTime`.
* Checks if the auction has ended.
* If there's a highest bidder:
* Transfers the NFT to the highest bidder.
* Sends the `highestBid` amount to the `seller`.
* Emits the `AuctionEnded` event.
* If there are no bidders, returns the NFT to the seller.
* **`cancelAuction()` Function:**
* Allows the seller to cancel the auction *before* it ends.
* Transfers the NFT back to the seller.
* Emits the `AuctionCancelled` event.
* **`receive()` Function:**
* A fallback function to receive Ether. This is necessary for the contract to receive bid amounts.
**JavaScript (for interacting with the contract):**
```javascript
const ethers = require('ethers');
// Replace with your contract address and ABI
const contractAddress = 'YOUR_CONTRACT_ADDRESS'; // Replace with deployed contract address
const contractABI = [
// Paste your contract's ABI here (from Remix or Hardhat output)
{
"inputs": [
{
"internalType": "address",
"name": "_nftContract",
"type": "address"
},
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_auctionDuration",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "winner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "AuctionEnded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "seller",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "endTime",
"type": "uint256"
}
],
"name": "AuctionStarted",
"type": "event"
},
{
"inputs": [],
"name": "auctionEndTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "bid",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "endAuction",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "highestBid",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "highestBidder",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nftContract",
"outputs": [
{
"internalType": "contract IERC721",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "seller",
"outputs": [
{
"internalType": "address payable",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
];
// Replace with your Ethereum provider URL (e.g., Infura, Alchemy, local Ganache)
const providerUrl = 'YOUR_PROVIDER_URL'; //Example: 'http://localhost:8545'
const provider = new ethers.providers.JsonRpcProvider(providerUrl);
// Replace with your wallet's private key (for signing transactions) - USE WITH CAUTION!
const privateKey = 'YOUR_PRIVATE_KEY'; // NEVER commit your private key to version control! Use environment variables.
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddress, contractABI, wallet);
async function placeBid(amountInEther) {
try {
const amountInWei = ethers.utils.parseEther(amountInEther);
const tx = await contract.bid({ value: amountInWei });
console.log('Bid placed, transaction hash:', tx.hash);
await tx.wait(); // Wait for the transaction to be mined
console.log('Bid confirmed!');
} catch (error) {
console.error('Error placing bid:', error);
}
}
async function endAuction() {
try {
const tx = await contract.endAuction();
console.log('Ending auction, transaction hash:', tx.hash);
await tx.wait();
console.log('Auction ended!');
} catch (error) {
console.error('Error ending auction:', error);
}
}
async function getAuctionDetails() {
try {
const auctionEndTime = await contract.auctionEndTime();
const highestBid = await contract.highestBid();
const highestBidder = await contract.highestBidder();
console.log('Auction End Time:', new Date(auctionEndTime * 1000)); // Convert to human-readable date
console.log('Highest Bid:', ethers.utils.formatEther(highestBid), 'ETH');
console.log('Highest Bidder:', highestBidder);
} catch (error) {
console.error("Error fetching auction details:", error);
}
}
// Example Usage (call these functions as needed)
async function main() {
await getAuctionDetails(); // Example: Fetch auction details
//await placeBid("0.01"); // Example: Place a bid of 0.01 ETH
//await endAuction(); // Example: End the auction
}
main();
```
**Explanation (JavaScript):**
* **`ethers`**: Imports the `ethers.js` library for interacting with Ethereum.
* **`contractAddress`**: Replace this with the address of your deployed `Auction` contract.
* **`contractABI`**: Replace this with the ABI (Application Binary Interface) of your `Auction` contract. The ABI is a JSON array that describes the contract's functions, events, and data structures. You can usually find the ABI in the output of your Solidity compiler (e.g., Remix, Hardhat, Truffle).
* **`providerUrl`**: Replace this with the URL of an Ethereum provider (e.g., Infura, Alchemy, or a local Ganache instance).
* **`privateKey`**: Replace this with the private key of the Ethereum account you want to use to interact with the contract. **IMPORTANT:** Never commit your private key to version control. Use environment variables or a secure key management system.
* **`provider`**: Creates an `ethers.providers.JsonRpcProvider` instance to connect to the Ethereum network.
* **`wallet`**: Creates an `ethers.Wallet` instance, which represents your Ethereum account and is used to sign transactions.
* **`contract`**: Creates an `ethers.Contract` instance, which allows you to interact with the smart contract.
* **`placeBid(amountInEther)` Function:**
* Takes the bid amount in Ether as an argument.
* Converts the Ether amount to Wei (the smallest unit of Ether).
* Calls the `bid()` function on the contract, sending the Wei amount as the `value`.
* Waits for the transaction to be mined and confirms the bid.
* Handles errors.
* **`endAuction()` Function:**
* Calls the `endAuction()` function on the contract.
* Waits for the transaction to be mined and confirms the auction end.
* Handles errors.
* **`getAuctionDetails()` Function:**
* Fetches auction details (end time, highest bid, highest bidder) from the contract.
* Formats the output for readability.
* **`main()` Function:**
* Example function that calls the other functions to demonstrate how to interact with the contract. You would modify this to fit your specific use case.
**Setup and Usage:**
1. **Install Dependencies:**
```bash
npm install ethers @openzeppelin/contracts
```
2. **Deploy the Solidity Contract:**
* Use a tool like Remix, Hardhat, or Truffle to compile and deploy the `Auction.sol` contract to a test network (like Ganache) or a public testnet (like Goerli or Sepolia).
* Make sure you have some test ETH in your account to pay for gas.
3. **Configure the JavaScript Code:**
* Replace `YOUR_CONTRACT_ADDRESS`, `YOUR_PROVIDER_URL`, and `YOUR_PRIVATE_KEY` in the JavaScript code with the appropriate values. **Be extremely careful with your private key!**
4. **Run the JavaScript Code:**
```bash
node your-script-name.js // Replace with the name of your JavaScript file
```
**Important Considerations:**
* **Security:** This is a simplified example and lacks many security features needed for production deployments. Specifically:
* **Reentrancy Attacks:** While using OpenZeppelin's `ReentrancyGuard` mitigates simple reentrancy, more complex attacks are still possible. Careful auditing is essential.
* **Denial-of-Service (DoS):** Consider edge cases that could prevent the auction from ending or refunds from being processed.
* **Integer Overflow/Underflow:** Solidity 0.8.0 and later include built-in overflow/underflow checks. If you're using an older version, use SafeMath.
* **Front-Running:** Bidders can see transactions in the mempool and try to place slightly higher bids before yours is mined. Solutions include commit-reveal schemes or using a private transaction service.
* **Gas Optimization:** The contract can be optimized to reduce gas costs. Consider using storage variables efficiently, avoiding unnecessary loops, and using assembly code for gas-intensive operations.
* **Error Handling:** The JavaScript code includes basic error handling, but you should implement more robust error handling and logging in a production application.
* **User Interface:** This example provides a basic command-line interface. You'll typically want to build a web-based user interface to make the auction accessible to users.
* **NFT Ownership:** The example assumes the seller owns the NFT at the time of contract creation. You might want to add checks to ensure that the seller still owns the NFT when the auction ends.
* **Time Sensitivity:** Smart contract time is based on block timestamps, which can be manipulated by miners to some extent. This is generally not a major issue, but it's something to be aware of.
* **Testing:** Thoroughly test your contract with different scenarios to ensure it behaves as expected. Use tools like Hardhat or Truffle for testing.
This comprehensive example should get you started building your NFT auction smart contract! Remember to always prioritize security and test your code thoroughly before deploying to a live network. Good luck!
👁️ Viewed: 9
Comments