# 研究 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/