前言本文通过借助openzeppelin和solidity编写一个荷兰拍卖的合约,合约主要实现了设置拍买开始时间,拍卖并铸造,提现,实时获取拍卖价格等相关功能荷兰拍买以及说明荷兰拍买概念:一种特殊的拍卖形式,也称“减价拍卖”。其特点是拍卖标的的竞价由高到低依次递减,直到第一个竞买人应价(
本文通过借助openzeppelin和solidity编写一个荷兰拍卖的合约,合约主要实现了设置拍买开始时间,拍卖并铸造,提现,实时获取拍卖价格等相关功能
荷兰拍买以及说明
荷兰拍买概念:
一种特殊的拍卖形式,也称“减价拍卖”。其特点是拍卖标的的竞价由高到低依次递减,直到第一个竞买人应价(达到或超过底价)时击槌成交;<br>
荷兰拍买特点:
- 价格递减:拍卖从一个较高的起始价开始,然后逐渐降低,直到有竞买人接受当前价格。
- 迅速成交:这种拍卖方式成交迅速,适用于需要快速交易的物品。
- 信息流和物流同步:在拍卖过程中,信息流和物流同步运行,确保拍卖的高效性。
荷兰拍买优缺点:
优点:
缺点:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "hardhat/console.sol";
contract DutchAuction is Ownable, ERC721 {
uint256 public constant COLLECTION_SIZE = 10000; // NFT总数
uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价
uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价)
uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,设为10分钟
uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // 每过多久时间,价格衰减一次
uint256 public constant AUCTION_DROP_PER_STEP =
(AUCTION_START_PRICE - AUCTION_END_PRICE) /
(AUCTION_TIME / AUCTION_DROP_INTERVAL); // 每次价格衰减步长
uint256 public auctionStartTime; // 拍卖开始时间戳
string private _baseTokenURI; // metadata URI
uint256[] private _allTokens; // 记录所有存在的tokenId
//设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过`setAuctionStartTime(uint32)`函数来调整
constructor() Ownable(msg.sender) ERC721("BTF Dutch Auction", "BTF Dutch Auction") {
auctionStartTime = block.timestamp;
}
/**
* ERC721Enumerable中totalSupply函数的实现
*/
function totalSupply() public view virtual returns (uint256) {
return _allTokens.length;
}
/**
* Private函数,在_allTokens中添加一个新的token
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokens.push(tokenId);
}
// 拍卖mint函数
function auctionMint(uint256 quantity) external payable{
uint256 _saleStartTime = uint256(auctionStartTime); // 建立local变量,减少gas花费
require(
_saleStartTime != 0 && block.timestamp >= _saleStartTime,
"sale has not started yet"
); // 检查是否设置起拍时间,拍卖是否开始
require(
totalSupply() + quantity <= COLLECTION_SIZE,
"not enough remaining reserved for auction to support desired mint amount"
); // 检查是否超过NFT上限
uint256 totalCost = getAuctionPrice() * quantity; // 计算mint成本
// console.log(msg.value >= totalCost);
// console.log(msg.value);
// console.log(totalCost);
require(msg.value >= totalCost, "Need to send more ETH."); // 检查用户是否支付足够ETH
// Mint NFT
for(uint256 i = 0; i < quantity; i++) {
uint256 mintIndex = totalSupply();
_mint(msg.sender, mintIndex);
_addTokenToAllTokensEnumeration(mintIndex);
}
// 多余ETH退款
if (msg.value > totalCost) {
payable(msg.sender).transfer(msg.value - totalCost); //注意一下这里是否有重入的风险
}
}
// 获取拍卖实时价格
function getAuctionPrice()
public
view
returns (uint256)
{
if (block.timestamp < auctionStartTime) {
return AUCTION_START_PRICE;
}else if (block.timestamp - auctionStartTime >= AUCTION_TIME) {
return AUCTION_END_PRICE;
} else {
uint256 steps = (block.timestamp - auctionStartTime) /
AUCTION_DROP_INTERVAL;
return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
}
}
// auctionStartTime setter函数,onlyOwner
function setAuctionStartTime(uint32 timestamp) external onlyOwner {
auctionStartTime = timestamp;
}
// BaseURI
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
// BaseURI setter函数, onlyOwner
function setBaseURI(string calldata baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
// 提款函数,onlyOwner
function withdrawMoney() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
}
# 编译指令
# npx hardhat compile
说明:关于设置开拍实际需要使用Unix时间格式,可以通过Unix在线网站获取
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("荷兰拍卖",function(){
let DutchAuction;//合约
let firstAccount//第一个账户
let secondAccount//第二个账户
let addr1;
let addr2;
beforeEach(async function(){
await deployments.fixture(["DutchAuction"]);
[addr1,addr2] = await ethers.getSigners();//这样获取的账号可以进行转账操作
firstAccount=(await getNamedAccounts()).firstAccount;
secondAccount=(await getNamedAccounts()).secondAccount;
const DutchAuctionDeployment = await deployments.get("DutchAuction");
DutchAuction = await ethers.getContractAt("DutchAuction",DutchAuctionDeployment.address);//已经部署的合约交互
})
describe("DutchAuction",function(){
it("DutchAuction 交易", async function () {
const balance = await ethers.provider.getBalance(firstAccount);
// 将余额转换为ETH单位
const balanceInEth = ethers.utils.formatEther(balance);
console.log(`Owner balance 未提款前: ${balanceInEth} ETH`);
//可以使用https://tool.chinaz.com/tools/unixtime.aspx网站获取Unix时间戳
//设置起拍时间
let timestamp = "1736499660";
let startTimemap=await DutchAuction.setAuctionStartTime(1736499660);
// console.log("设置起拍时间",startTimemap);
const date = new Date(timestamp * 1000)
let startTime=date.toLocaleString()
console.log("起拍时间Unix格式转日期",startTime);
// 获取总量
const totalSupply = await DutchAuction.totalSupply();//代币1的余额
console.log("当前总量",totalSupply)
//获取当前起拍价
let AuctionPrice=await DutchAuction.getAuctionPrice()
console.log("价格",`${ethers.utils.formatEther(AuctionPrice)} eth`)
//通过链接账号addr1铸造一个nft 给账号1eth 多的就退会原账号
await DutchAuction.connect(addr1).auctionMint(1,{value:ethers.utils.parseEther("1")})
console.log('当前总量',await DutchAuction.totalSupply())
//通过链接账号addr2铸造一个nft 给账号2eth 多的就退会原账号
await DutchAuction.connect(addr2).auctionMint(1,{value:ethers.utils.parseEther("1")})
console.log('当前总量',await DutchAuction.totalSupply())
//把拍卖行的提现
await DutchAuction.withdrawMoney();
console.log(`Owner balance 提款后: ${balanceInEth} ETH`);
})
})
})
# 测试指令
# npx hardhat test ./test/xxx.js
module.exports = async function ({getNamedAccounts,deployments}) {
const firstAccount= (await getNamedAccounts()).firstAccount;
const {deploy,log} = deployments;
const DutchAuction=await deploy("DutchAuction",{
from:firstAccount,
args: [],//参数
log: true,
})
console.log('荷兰拍卖合约地址',DutchAuction.address)
};
module.exports.tags = ["all", "DutchAuction"];
以上就是荷兰拍卖智能合约从开发到测试再到部署的全部过程,主要借助了工具在线的unix在线工具,以及openzeppelin的库,注意的是在测试auctionMint方法时需要链接账号后,对此合约发送对应的代币才可以测试通过。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!