--- lang: zh-tw tags: Notes, Cryptocurrency date: 20220324 robots: noindex, nofollow license: GPL-3.0 --- 2022-03-28 自己發 NFT—Part 1 === 歡迎加入 Discord 聊天室「清交區塊鏈DAO」:https://discord.com/invite/rpQy37a8tk 這次讀書會延續上週 Tommy 的分享,把主題轉向另一種熱門代幣種類:ERC721(NFT)。 我們會先使用 Remix 和 Pinata 快速發行最基本的 NFT。接著,透過閱讀 EIP721 提案內容,來看看 EIP721 和 EIP20 之間的差別在哪邊。 這份教學只是發行 NFT 的入門,對於實際掌握所有 NFT 使用技巧仍有相當差距;鼓勵各位讀者閱讀文末的連結,並在 Discord 聊天室與大家討論,相信會有更多收穫。 Remix+Pinata/NFT.Storage --- 1. 開啟 [Remix](https://remix.ethereum.org/) 2. 創立一個新檔案 `MyNFT.sol` 並貼上這份 solidity code ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.1; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; contract MyNFT is ERC721URIStorage { address private immutable _owner; uint256 private _highest_id; constructor() ERC721("MyNFT", "MNFT") { _owner = msg.sender; } function mintNFT(string calldata token_URI) public { require(msg.sender == _owner, "You are not owner."); uint256 new_token_ID = _highest_id; _highest_id += 1; _safeMint(msg.sender, new_token_ID); _setTokenURI(new_token_ID, token_URI); } } ``` 3. 編譯合約、部屬合約到 **Rinkeby** testnet 4. 登入 IPFS SaaS 服務 - 登入 [Pinata](https://www.pinata.cloud/) - 登入 [NFT.Storage](https://nft.storage/) 5. 上傳圖片到 IPFS SaaS 服務,並取得 `<Pic_URL>` - Pinata:點擊檔名旁邊的👁圖示 - NFT.Storage:Actions → Copy IPFS URL 6. 把 `<Pic_URL>` 填入以下 JSON 格式當中 ```json { "description": "Hello, this is my first NFT.", "image": "<Pic_URL>", "name": "MyNFT No.0" } ``` 7. 把上述 JSON 存成檔案並上傳到 IPFS SaaS,然後取得 `<Metadata_URL>` - Pinata:點擊檔名旁邊的👁圖示 - NFT.Storage:Actions → Copy IPFS URL 8. 回到 Remix,把 `<Metadata_URL>` 塞給 `mintNFT()` 函數的 `token_URI` 欄位 9. 按下按鈕送出 txn,等待 txn 被成功 mined 10. 前往 [OpenSea](https://testnets.opensea.io) **testnet** 搜尋合約地址就能看到稍早鑄造的 NFT 以下提供一個操作完成之後的例子;不需要完全相同,實際情形依個人操作而有所不同 - https://rinkeby.etherscan.io/address/0xa52705cbc4931f11161b45212df417c54b345c91 - https://testnets.opensea.io/collection/mynft-y7rsblfbfy - 出現「y7rsBlFbfy」字串是因為 OpenSea testnet 上面,已經存在其他 NFT project 是以相同名稱 MyNFT 命名,所以由系統自動加上以免搜尋出衝突的結果,不是合約本身的問題 閱讀 EIP-721 --- - https://eips.ethereum.org/EIPS/eip-721 ```solidity interface ERC721 { // Events event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); // Functions (Read-Only Methods) function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool); // Functions (Write Methods) function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; } ``` 重點解析 - 一個地址可以擁有多顆 token - 由 token 持有人(owner)一顆一顆授予權限給某地址實在太沒效率,因此設計有 `approve()` 和 `setApprovalForAll()` 兩種函數 - 由 owner 呼叫 `approve(address(0), token_id)` 代表撤銷權限 - 由 owner 呼叫 `setApprovalForAll(operator, false)` 代表撤銷權限 - `approve()` 負責設定「**地址**與單獨**一顆 token** 之間關聯性」 - 換句話說:Owner `approve()` the authorized address for managing `token_id` token. - 例如:OpenZeppelin 的實作細節就是在維護 state variable `_tokenApprovals` - 呼叫這個函數的 EOA,一定要是 token owner - 轉移 token 所有權時,一定要呼叫 `approve(address(0), token_id)` - 一個 token 只能有一個 approved address(以 OpenZeppelin 的實作來說) - approved address 可與 operator address 不同 - `setApprovalForAll()` 負責設定「**地址**與**地址**之間關聯性」 - 換句話說:Owner `setApprovalForAll()` of his/her owned tokens to the operator address. - 例如:OpenZeppelin 的實作細節就是在維護 state variable `_operatorApprovals` - 呼叫這個函數的 EOA,一定要是 token owner - 轉移 token 所有權時,不干這個函數的事情(token owner 可以換人,operator 不一定要換人) - 一個 address 只能有一個 operator address(以 OpenZeppelin 的實作來說) - operator address 可與 approved address 不同 - `safeTransferFrom()` 和 `transferFrom()` 的差別 - 函數是否幫忙檢查:如果 token 轉移過去的地址是合約,那麼該合約具備 `IERC721Receiver` 嗎? - `_isApprovedOrOwner()` - 這個函數的實做細節舉例三種「能夠花用特定某一顆 token」的條件 - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/76eee35971c2541585e05cbf258510dda7b2fbc6/contracts/token/ERC721/ERC721.sol#L232-L236 Further reading --- - EIP-721 & (OpenZeppelin's) Implementation - https://eips.ethereum.org/EIPS/eip-721 - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol - https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/extensions - Remix - https://remix.ethereum.org - https://remix-ide.readthedocs.io - Pinata - https://www.pinata.cloud - NFT.Storage(Made by Protocol Labs) - https://nft.storage/docs/ - OpenSea - https://docs.opensea.io/docs/metadata-standards - Chainlink 水龍頭 - https://faucets.chain.link/rinkeby