# 研究 MetaMask
[ToC]
## 知識點
### 常用第三方 web 錢包
- Metamask
- https://metamask.io/
- browser support: Chrome, Firefox, Brave, Edge
- App support: Android, iOS
- Coinbase Wallet
- https://www.coinbase.com/wallet
- Others
- https://coin98.com/
- (China) https://www.tokenpocket.pro/
- https://www.binance.com/en/wallet-direct
### 常用第三方 library
- web3.js
- https://web3js.readthedocs.io
- etheres.js
- https://docs.ethers.io
### web3.js/ethers.js library API 官方文件,沒有清楚定義何謂 address
- 何謂 address
- address 分為兩種
- wallet address
- contract address
- 不論是 Web3.js 還是 ehters.js library API 文件,都不會特別寫清楚參數 `address` 是 wallet address 還是 contract address
### 取得指定 address 的所有 transactions
- 不論是 web3.js 還是 ethers.js library API,都沒有這些功能:
- 取得指定 wallet address 的所有 Transactions
- 取得指定 contract address 的所有 Transactions
- 大家的做法:
- 使用爬蟲的概念,以 address 及 block number 一個一個 block 爬回來後,再做判斷,留下需要的資料
## Metamask
如果使用者有安裝 MetaMask 的話,window.ethereum 會有東西,且 ethereum.isMetaMask 會是 true
1. ethereum.selectedAddress 錢包地址(string),如果沒有授權會是 null => 已棄用,請改用 2 的方式拿 wallet address
2. ethereum.request({ method: 'eth_requestAccounts' }) 取得 wallet address
- 需要注意:授權錢包地址時選擇多個,MetaMask 也只認第一個,會以 array['0x...'] 的方式回傳(wallet address 會是小寫)
3. accountsChanged 監聽 wallet address 變更
4. chainChanged 監聽 network 變更
5. wallet_requestPermissions 也是授權錢包地址,選擇多個之後是回傳多個地址
- 但 MetaMask 還是只認第一個,如果第一個錢包地址沒有改變,就不會觸發 accountsChanged
- 舉例使用者授權 A, B,後來移除了 B,並不會觸發 accountsChanged,因為第一個錢包地址仍是 A
### track token
`wallet_watchAsset` 目前只支援 ERC20
```
options: {
address: string; // The address of the token contract
symbol: string; // A ticker symbol or shorthand, up to 5 characters
decimals: number; // The number of token decimals
image: string; // A string url of the token logo
};
```
MetaMask 文件中的範例資料:
```
type: 'ERC20',
options: {
address: '0xd00981105e61274c8a5cd5a88fe7e037d935b513',
symbol: 'TUT',
decimals: 18,
image: 'http://placekitten.com/200/300',
}
```
問題點:options 資料無誤後,進入 MetaMask 確認時(此時就會回傳 true),如果使用者點選取消也無法偵測到。
ref: [Doc](https://docs.metamask.io/guide/registering-your-token.html#code-free-example)
ref: [Doc](https://docs.metamask.io/guide/rpc-api.html#unrestricted-methods)
---
## Test Network
使用 MetaMask 並且開啟 Test Network 就會有以下四種 Test Network 可供使用:
- Rinkeby
- Opensea testnet 為 Rinkeby
- Kovan
- Ropsten
- Goerli/Görli
各條 Test Network 要弄到 ether 的 faucets 請到以下頁面找
ref: https://ethereum.org/en/developers/docs/networks/#testnets
四者差異 https://ethereum.stackexchange.com/a/30072
---
## Web3.js (v1.7.3)
### web3.eth.getAccounts
取得 wallet address,回傳格式為 array['0x...'](大寫)
### web3.utils.toChecksumAddress
轉大小寫,不過 web3.eth.getAccounts 回傳的 wallet address 是大寫,就不需要用 web3.utils.toChecksumAddress 轉了
### web3.eth.net.getNetworkType
取得 network 資訊
### web3.eth.getBalance
取得 balance 資訊,單位為 wei
### web3.utils.fromWei(num, 'ether')
轉換單位 wei to eth
### web3.eth.getBlockNumber
取得 current block number
### web3.eth.getPastLogs
ref: [Doc](https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#getpastlogs)
- 取得 **contract** address transaction log
- 需要提供 fromBlock, toBlock 跟 address,fromBlock 跟 toBlock 預設會是 latest block
- 最有問題的是 address,文件描述:`address - String|Array: An address or a list of addresses to only get logs from particular account(s).`
- 看起來好像給 wallet address 也撈的到對吧?答案是不行,測試過用在 block 區間紀錄的 wallet address 回傳空陣列
- formBlock 跟 toBlock 也不能給太大,如果資料筆數超過 10,000 筆會噴錯,並不能一次爬到所有資料
- 活躍的 contract address: 0x283af0b28c62c092c9727f1ee09c02ca627eb7f5
- 回傳資料的 address 是指 contract address,要撈資料請用 transactionHash 搭配 `web3.eth.getTransaction` 使用
### web3.eth.getTransaction(hash)
取得 transaction 資訊
### 計算 Gas Fee
```
var gasPrice = await web3.eth.getGasPrice();
var estimateGas = await web3.eth.estimateGas({
to: WALLET_ADDRESS
});
var cost = estimateGas * gasPrice;
var cost_eth = web3.utils.fromWei(cost.toString(), 'ether');
```
但這樣算出來會比 MetaMask 算出來的 Gas Fee 少一點
先不自己算,反正算好也沒用
### Send Transaction
```
web3.eth.sendTransaction({
from: sender_wallet_address,
to: receiver_wallet_address,
value: (eth)
})
.then(function(receipt){
// 回傳 receipt
}).catch((error) => {
// 使用者取消也會進 error
});
```
- 文件上 chain 預設是 mainnet,留空在串接時有偵測到目前連接到哪個 network,沒有因為沒給就走 mainnet
- value 單位用 wei 也行,只是太小的話 MetaMask 會顯示 0,還是用 eth 比較適合
---
## Mint NFT (ERC721)
### 透過 Remix deploy contract + mint token
url: https://remix.ethereum.org/
Step1. Importing the contract
建立新檔案 `OpenZeppelinPresetContracts.sol`,並將以下複製貼上
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/presets/ERC721PresetMinterPauserAutoId.sol";
contract MCAT is ERC721PresetMinterPauserAutoId {
constructor() public
ERC721PresetMinterPauserAutoId("Meow Meow", "MCAT", "https://my-json-server.typicode.com/abcoathup/samplenft/tokens")
{}
// This allows the minter to update the tokenURI after it's been minted.
// To disable this, delete this function.
function setTokenURI(uint256 tokenId, string memory tokenURI) public {
require(hasRole(MINTER_ROLE, _msgSender()), "web3 CLI: must have minter role to update tokenURI");
setTokenURI(tokenId, tokenURI);
}
}
```
Step2. Complie the contract
對 `OpenZeppelinPresetContracts.sol` 進行 complie,會進到 `SOLIDITY COMPILER` 介面
確認 COMPILER 為 `0.6.2` 後按 complie
Step3. Deploy the contract
`DEPLOY & RUN TRANSACTIONS` 介面,請依序編輯欄位
ENVIRONMENT `Injected Web3`
Testnet Network `Rinkeby`
GAS LIMIT `5000000`
CONTRACT `MCAT` => 前面 complie 的就是這個
按下 Deploy 後,會切換到 MetaMask 介面確認 deploy contract 費用,送出之後等待 Deploy 完成
Step4. Mint token
完成 Deploy 後,在 `Deployed Contracts` 區塊會看到 `MACT AT 0x...`,點擊展開之後找到 Mint 按鈕,填下收 Token 的 wallet address 之後按下 Mint,會切換到 MetaMask 介面確認費用,送出之後等待 Mint 完成
Step5. Import Token
在 MetaMask 找到 `Import tokens` 連結,點擊後填下 `contract address` 以及 `token ID` 送出即可
Step6. Validate Token
至 https://testnets.opensea.io/assets/[contract address]/[token ID] 查看
如果輸入了尚未被 mint 的 token ID,會顯示 404 => 像是只 mint 一次,但輸入 token ID: 1
參考文章:
1. https://forum.openzeppelin.com/t/create-an-nft-and-deploy-to-a-public-testnet-using-remix/6358
2. https://www.frank.hk/blog/nft-smart-contract/
### Mint ERC1155 Token
### 透過 Remix deploy contract + mint token
url: https://remix.ethereum.org/
Step1. localhost 建立專案
```
mkdir remix-contract && cd remix-contract
npm init
npm install @openzeppelin/contracts
npm install @remix-project/remixd
mkdir contracts && touch ERC1155.sol
```
Step2. edit contract/ERC1155.sol
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol";
contract Marvel1155 is ERC1155PresetMinterPauser {
uint256 public constant CAPTAIN_AMERICA = 0;
uint256 public constant THOR = 1;
uint256 public constant IRON_MAN = 2;
uint256 public constant SPIDER_MAN = 3;
constructor() ERC1155PresetMinterPauser( "https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json") {
_mint(msg.sender, CAPTAIN_AMERICA, 10**18, "");
_mint(msg.sender, THOR, 1, "");
_mint(msg.sender, IRON_MAN, 5, "");
_mint(msg.sender, SPIDER_MAN, 10, "");
}
}
```
Step3. connect localhost
在 Remix IDE 選擇連結 localhost
```
remixd -s <absolute-path-to-the-shared-folder> --remix-ide https://remix.ethereum.org
```
Step4. complie
對 ERC1155.sol 進行 complie,會進到 SOLIDITY COMPILER 介面
確認 COMPILER 為 `0.8.7` 後按 complie
Step5. deploy contract
`DEPLOY & RUN TRANSACTIONS` 介面,請依序編輯欄位
ENVIRONMENT `Injected Web3`
Testnet Network `Rinkeby`
GAS LIMIT `5000000`
CONTRACT `Marvel1155` => 前面 complie 的就是這個
按下 Deploy 後,會切換到 MetaMask 介面確認 deploy contract 費用,送出之後等待 Deploy 完成。
按照範例若 deploy 成功,會有 4 種 ERC-1155 token,接下來確認一下
在 `https://testnets.opensea.io/` 用 contract address 搜尋,應出現 Collection 資料
contract address `0xa175f875e00583daca81269f815a2cb3420a94bc`
collection url `https://testnets.opensea.io/collection/unidentified-contract-nyrdxzi3sa`
Step6. Mint token
完成 Deploy 後,在 `Deployed Contracts` 區塊會看到 `MARVEL1155 AT 0x...`,點擊展開之後找到 Mint 按鈕,依序填完資料。
to: (wallet address)
id: 4
amount: 1
data: 0x00
按下 transact,會切換到 MetaMask 介面確認費用,送出之後等待 Mint 完成
重整 collection 會看到第五個 token `HULK`
參考文章:
1. https://www.frank.hk/blog/nft-erc1155/