---
title: 'Solidity WTF 103 35 單元 荷蘭拍賣'
lang: zh-tw
---
Solidity WTF 103 35 單元 荷蘭拍賣
===
:::info
:date: 2024/10/13
:::
[TOC]
# 荷蘭拍賣
是一種銷售拍賣方式,透過荷蘭拍賣發售ERC721的NFT。荷蘭拍賣也稱為減價拍賣,價格由高至低遞減的一種拍賣方式,直到競賣人應價(達到或是超過底價)時成交的一種拍賣。
>Azuki 和 World of Women 都是使用這種方式
使用這種方式有兩種原因:
- 項目方獲益最大,因為價格是由高至低,想要這件NFT的用戶可能會在很高點應價。
- 拍賣時間很長,避免gas war。
## 宣告變數
會有幾個狀態變量需要宣告
- NFT總量
- 起拍價,也是最高價
- 結束價,最低價/地板價
- 拍賣持續時間
- 每過多少時間,價格會衰減一次
- 拍賣開始時間
```javascript!
uint256 public constant COLLECTOIN_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); // 每次價格衰減步長
```
:::warning
`AUCTION_DROP_PER_STEP`:
- 分子部分:`AUCTION_START_PRICE` - `AUCTION_END_PRICE`
這代表從拍賣開始到結束,價格總共下跌的總幅度。比如,從 `1 ether` 跌到 `0.1 ether`,總共下跌 `0.9 ether`。
- 分母部分:`AUCTION_TIME` / `AUCTION_DROP_INTERVAL`
這部分代表拍賣過程中,總共經歷了多少次價格衰減的步驟(`step`)。比如,如果總拍賣時間是 `10` 分鐘,而每分鐘價格下跌一次,那麼總共會有 `10` 步(步驟)。
:::
## 函數
> 需要 set 起拍時間
```javascript!
function setAuctionStartTime(uint32 timestamp) external onlyOwner {
auctionStartTime = timestamp;
}
```
> 獲取拍賣當下的價格
>> 區塊時間若小於起始時間,價格為最高價。
>> 區塊時間若大於結束時間,價格為最低價。
>> 區塊時間若處於兩者之間,則計算當前衰減價格。
```javascript!
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 {
// steps 計算的是當前已經經過了多少個價格遞減的間隔(AUCTION_DROP_INTERVAL)。這是通過將已經過去的時間(block.timestamp - auctionStartTime)除以每次價格遞減的時間間隔來得到的。
uint256 steps = (block.timestamp - auctionStartTime) /
AUCTION_DROP_INTERVAL;
// 根據已經過了多少個間隔,計算總共應該降低多少價格(steps * AUCTION_DROP_PER_STEP),最後從起拍價中扣除這個金額來得到當前的價格。
return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
}
}
```
## mint
支付ETH參加拍賣並鑄造NFT
>先檢查是否開始/鑄造是否超出NFT總量,通過getAuctionPrice()和鑄造數量計算拍賣成本,並檢查用戶支付ETH是否足夠; 如果足夠將NFT鑄造給用戶並退回多出的ETH,反之,退回交易(revert)。
```javascript!
function auctionMint(uint256 quantity) external payable{
uint256 _saleStartTime = uint256(auctionStartTime); // 建立 local 變數,減少 gas 消耗
require(
_saleStartTime != 0 && block.timestamp >= _saleStartTime,
"拍賣尚未開始"
); // 檢查是否已設置起拍時間,以及拍賣是否已開始
require(
totalSupply() + quantity <= COLLECTOIN_SIZE,
"剩餘拍賣預留的 NFT 數量不足,無法支持所需的鑄造數量"
); // 檢查是否超過 NFT 限量
uint256 totalCost = getAuctionPrice() * quantity; // 計算鑄造成本
require(msg.value >= totalCost, "需要支付更多的 ETH."); // 檢查用戶是否支付足夠的 ETH
// 鑄造 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); // 注意是否存在重入風險
}
}
```
## 提款函數
項目方通過此函數提走拍賣籌集的ETH。
```javascript!
function withdrawMoney() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
```
:::success
這邊是簡化的荷蘭拍賣,透過此拍賣方式發售ERC721標準NFT。
:::