# 第二週作業 ## 基本題 ### 1. 部署一個自定義的 ERC20 Token 在 Goerli 鏈上 * 開源合約並提交合約地址 合約地址URL:https://goerli.etherscan.io/address/0xd0fa61a4c4fa1057b9104b390fa2221b9eebb561 Token Name : RyanToken Token Symbol:RT * 實作 mint 和 burn 功能 ```solidity= /** *Submitted for verification at Etherscan.io on 2023-03-05 */ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.18; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { uint8 private _decimals; //Token最小單位 //初始化 constructor( string memory name_, string memory symbol_, uint8 decimals_ ) ERC20(name_, symbol_) { _decimals = decimals_; } //取得Token最小單位 function decimals() public view override returns (uint8) { return _decimals; } //實作 mint 鑄造 function mint(address account, uint256 amount) external { super._mint(account, amount); } //實作 burn 燒毀 function burn(address account, uint256 amount) external { super._burn(account, amount); } } ``` * 轉移 100 個 Token 到以下地址 0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3 Transaction Details URL:https://goerli.etherscan.io/tx/0xb25311cac96754ea2bf158073bd09938756bf8af67e9d2a9c3fbaae66074cd20 ![](https://i.imgur.com/IxebPit.png) * 轉移 Token 給所有組員 ![](https://i.imgur.com/oA8eTsF.png) ### 2.部署一個 ERC721 NFT 合約,同時擁有付費 mint 功能 * 開源合約並提交合約地址 * 使用 OpenSea Metadata 標準,並提交 OpenSea頁面 * 將檔案上傳至 ipfs * 擁有白名單機制並且將組員加入白名單 * 設定總量上限 * 加入至少一種自定義功能,例如:荷蘭拍、盲盒、返佣、融合… 合約地址:https://goerli.etherscan.io/address/0xe5d30e6e2f4a20d243e971dc31055c664203fd46#code ```solidity= /** *Submitted for verification at Etherscan.io on 2023-03-05 */ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.18; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyNFT is ERC721, Ownable { bytes32 private root; //root hash uint256 private tokenId = 0; //NFT Token ID bool private isBoxOpened = false; //盲盒控制打開與否 string private baseUri; //圖片uri string private unrevealedURI; //盲盒圖片uri uint256 private cost; // Mint 價格 uint256 private maxCount; //最大鑄造數量 constructor( string memory _name, string memory _symbol, string memory _baseUri, string memory _unrevealedURI, uint256 _cost, uint256 _maxCount ) ERC721(_name, _symbol) { baseUri = _baseUri; unrevealedURI = _unrevealedURI; cost = _cost; maxCount = _maxCount; } //驗證proof modifier verifyProof(bytes32[] memory proof) { require( MerkleProof.verify( proof, root, keccak256(abi.encodePacked(msg.sender)) ), "Invalid proof" ); _; } //驗證是否超過最大鑄造數量 modifier verifyMintCount() { require(tokenId < maxCount, "Can't mint more"); _; } //設定鑄造價格 function setCost(uint256 _cost) external onlyOwner { cost = _cost; } //取得鑄造價格 function getCost() external view returns (uint256) { return cost; } //設定鑄造最大數量 function setMaxCount(uint256 _maxCount) external onlyOwner { maxCount = _maxCount; } //取得鑄造最大數量 function getMaxCount() external view returns (uint256) { return maxCount; } //取得base Uri function _baseURI() internal view override returns (string memory) { return isBoxOpened ? baseUri : unrevealedURI; } //打開盲盒 function openBox() external onlyOwner { isBoxOpened = true; } //關閉盲盒 function closeBox() external onlyOwner { isBoxOpened = false; } //取得token Uri function tokenURI(uint256 _tokenId) public view override returns (string memory) { _requireMinted(_tokenId); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string( abi.encodePacked( baseURI, Strings.toString(_tokenId), ".json" ) ) : ""; } //鑄造給一般人,但要收錢 function mint() external payable verifyMintCount { require(msg.value == cost, "Not enough ether sent"); tokenId++; _safeMint(msg.sender, tokenId); } //免費鑄造給白名單 function whitelistMint(bytes32[] calldata _proof) external verifyProof(_proof) verifyMintCount { tokenId++; _safeMint(msg.sender, tokenId); } //設定Root function setRoot(bytes32 _root) external onlyOwner { root = _root; } //提取錢給合約擁有者 function withdraw() public onlyOwner { require(address(this).balance > 0, "Balance is 0"); payable(owner()).transfer(address(this).balance); } } ``` 盲盒 ![](https://i.imgur.com/e1UEUch.png) 解盲盒 ![](https://i.imgur.com/pMkbjjq.png) ## 進階題 ### 1.研究 ERC721A 合約 * 寫下 ERC721A 及 ERC721 差異 ERC-721 是一種合約協議的代號,他是目前 NFT 的基礎協議,用作發行擁有不同代號的有限量或無限量代幣(Token)。而因為他的唯一編碼方式以及使用 Metadata 來提供更多資訊的特色,因此被廣泛應用在各領域。 ERC-721A 是由 ERC-721 的改良,因原本使用的 ERC-721 其語法的因素,導致在鑄造(Mint)多個 NFT 時會產生大量的手續費(Gas Fee),而 ERC-721A 大幅度地減少了手續費的消耗,因此在目前的 NFT 世界被廣泛使用。 * 實際部署 ERC721A 合約比較所花費的 gas fee ERC721A ![](https://i.imgur.com/vrO7s09.png) ERC721 ![](https://i.imgur.com/MDvMxp9.png) ### 2.Contract Factory 使用合約來部署多個 ERC20 或 ERC721 合約 * 開源合約並提交合約地址 * 每個產出的子合約可以設定不同的參數,name, symbol... 合約地址 : https://goerli.etherscan.io/address/0xccb00d27ad3e98c492a4ee66c9bef994797fffcb ```solidity= /** *Submitted for verification at Etherscan.io on 2023-03-05 */ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.18; import "./ERC20.sol"; import "./ERC721A.sol"; contract Factory { address[] public erc20ContractArr; address[] public erc721ContractArr; //建立ERC20合約 function createERC20( string memory name_, string memory symbol_, uint8 decimals_ ) external payable { MyToken token = new MyToken(name_, symbol_, decimals_); erc20ContractArr.push(address(token)); } function getAllERC20Contract() external view returns (address[] memory) { return erc20ContractArr; } function createERC721A( string memory name_, string memory symbol_, string memory baseUri_, string memory unrevealedURI_, uint256 cost_, uint256 maxCount_ ) external payable { MyNFT nft = new MyNFT(name_,symbol_,baseUri_,unrevealedURI_,cost_,maxCount_); erc721ContractArr.push(address(nft)); } function getAllERC721AContract() external view returns (address[] memory) { return erc721ContractArr; } } ``` ###### tags: `Solidity 工程師實戰營第 5 期`