---
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