.env! 請務必不要與任何人分享或洩露您的 .env 檔案,因為這樣做會洩露您的機密。 如果您正在使用版本控制,請將您的 .env 新增到 gitignore 檔案中。
++ 只需加入你資產的連結、名稱和描述,然後按下「鑄造」。 +
+ + +{status}
++ {" "} + 🦊 + 你必須在瀏覽器中安裝 MetaMask,一個虛擬的以太坊錢包。 + +
+ + ), + } + } +} +``` + +讓我們一起分解這個程式碼的工作: + +首先,我們的函式會檢查你的瀏覽器中是否啟用了 `window.ethereum`。 + +`window.ethereum` 是由 MetaMask 和其他錢包提供者注入的全域 API,允許網站請求使用者的以太坊帳戶。 如果請求被接納,它能夠從連結成功的用戶那裡的區塊鏈中讀取數據,並且會向用戶提出簽署訊息和交易的建議。 查看 [MetaMask 文件](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)以獲得更多資訊! + +如果 `window.ethereum` _不存在_,那表示 MetaMask 沒有安裝。 這會回傳一個 JSON 物件,其中回傳的 `address` 是一個空字串,而 `status` JSX 物件則傳達使用者必須安裝 MetaMask 的訊息。 + +**我們撰寫的大部分函式都會回傳 JSON 物件,我們可以用它來更新我們的狀態變數和 UI。** + +現在如果 `window.ethereum` _存在_,事情就變得有趣了。 + +使用 try/catch 迴圈,我們將透過呼叫 [`window.ethereum.request({ method: "eth_requestAccounts" });`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts) 來嘗試連接 MetaMask。 呼叫這個函式會在瀏覽器中開啟 MetaMask,提示使用者將他們的錢包連接到你的去中心化應用程式。 + +- 如果使用者選擇連接,`method: "eth_requestAccounts"` 會回傳一個陣列,其中包含所有連接到此去中心化應用程式的使用者帳戶位址。 總而言之,我們的 `connectWallet` 函式會回傳一個 JSON 物件,其中包含此陣列中的_第一個_ `address` (見第 9 行) 和一則 `status` 訊息,提示使用者向智能合約寫入一則訊息。 +- 如果使用者拒絕連接,那麼 JSON 物件將包含一個空字串作為回傳的 `address`,以及一則反映使用者拒絕連接的 `status` 訊息。 + +### 將 connectWallet 函式新增至你的 Minter.js UI 元件 {#add-connect-wallet} + +既然我們已經寫好了 `connectWallet` 函式,就把它連接到我們的 `Minter.js` 元件吧。 + +首先,我們必須將函式匯入到 `Minter.js` 檔案中,方法是在 `Minter.js` 檔案的頂部新增 `import { connectWallet } from "./utils/interact.js";`。 你的 `Minter.js` 的前 11 行現在應該看起來像這樣: + +```javascript +import { useEffect, useState } from "react"; +import { connectWallet } from "./utils/interact.js"; + +const Minter = (props) => { + + //狀態變數 + const [walletAddress, setWallet] = useState(""); + const [status, setStatus] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [url, setURL] = useState(""); +``` + +然後,在我們的 `connectWalletPressed` 函式內部,我們將呼叫匯入的 `connectWallet` 函式,如下所示: + +```javascript +const connectWalletPressed = async () => { + const walletResponse = await connectWallet() + setStatus(walletResponse.status) + setWallet(walletResponse.address) +} +``` + +注意到我們大部分的功能是如何從 `interact.js` 檔案中抽象出來,與 `Minter.js` 元件分離的嗎? 這是我們跟M-V-C規範相容的做法! + +在 `connectWalletPressed` 中,我們只需對匯入的 `connectWallet` 函式進行一個 await 呼叫,並利用其回應,透過它們的 state hooks 更新我們的 `status` 和 `walletAddress` 變數。 + +現在,讓我們儲存 `Minter.js` 和 `interact.js` 這兩個檔案,並測試一下我們目前的 UI。 + +在「localhost:3000」打開你的瀏覽器,並在頁面的右上方按下按鍵「連結起錢包」。 + +如果你已安裝 MetaMask,系統應該會提示你將錢包連接到你的去中心化應用程式。 請接受進行連結的邀請。 + +你應會看得到錢包的按鈕現時反映了你的地址被連結好了。 + +接下來,試著重新整理頁面... 這很奇怪。 我們的錢包按鈕會鼓勵我們對MetaMask進行連結,就算它已經被連結好了。。。。。。 + +但是,請不要擔心! 我們可以透過實作一個名為 `getCurrentWalletConnected` 的函式輕鬆解決這個問題,該函式會檢查是否有位址已經連接到我們的去中心化應用程式,並相應地更新我們的 UI! + +### getCurrentWalletConnected 函式 {#get-current-wallet} + +在你的 `interact.js` 檔案中,新增以下 `getCurrentWalletConnected` 函式: + +```javascript +export const getCurrentWalletConnected = async () => { + if (window.ethereum) { + try { + const addressArray = await window.ethereum.request({ + method: "eth_accounts", + }) + if (addressArray.length > 0) { + return { + address: addressArray[0], + status: "👆🏽 請在上方文字欄位中寫入一則訊息。", + } + } else { + return { + address: "", + status: "🦊 請使用右上角的按鈕連接到 MetaMask。", + } + } + } catch (err) { + return { + address: "", + status: "😥 " + err.message, + } + } + } else { + return { + address: "", + status: ( + ++ {" "} + 🦊 + 你必須在瀏覽器中安裝 MetaMask,一個虛擬的以太坊錢包。 + +
+ + ), + } + } +} +``` + +這段程式碼與我們剛才寫的 `connectWallet` 函式_非常_相似。 + +主要的區別在於,我們不是呼叫 `eth_requestAccounts` 方法 (這會開啟 MetaMask 讓使用者連接他們的錢包),而是在這裡呼叫 `eth_accounts` 方法,它只會回傳一個包含當前連接到我們去中心化應用程式的 MetaMask 位址的陣列。 + +為了看到這個函式的實際作用,讓我們在 `Minter.js` 元件的 `useEffect` 函式中呼叫它。 + +就像我們對 `connectWallet` 所做的一樣,我們必須將這個函式從 `interact.js` 檔案匯入到 `Minter.js` 檔案中,如下所示: + +```javascript +import { useEffect, useState } from "react" +import { + connectWallet, + getCurrentWalletConnected, //在此匯入 +} from "./utils/interact.js" +``` + +現在,我們只需在 `useEffect` 函式中呼叫它: + +```javascript +useEffect(async () => { + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) +}, []) +``` + +注意,我們使用對 `getCurrentWalletConnected` 呼叫的回應來更新我們的 `walletAddress` 和 `status` 狀態變數。 + +一旦你已經添加好了這個程式碼,嘗試刷新我們的瀏覽器視窗。 這個按鈕應該會跟你說:「你已經連結好了。」,然後會顯出一個你錢包被連結好的地址的預視 - 就算在你刷新之後也會這樣! + +### 實作 addWalletListener {#implement-add-wallet-listener} + +我們去中心化應用程式錢包設定的最後一個步驟是實作錢包監聽器,這樣當我們錢包的狀態改變時 (例如使用者中斷連線或切換帳戶),我們的 UI 就會更新。 + +在你的 `Minter.js` 檔案中,新增一個 `addWalletListener` 函式,如下所示: + +```javascript +function addWalletListener() { + if (window.ethereum) { + window.ethereum.on("accountsChanged", (accounts) => { + if (accounts.length > 0) { + setWallet(accounts[0]) + setStatus("👆🏽 請在上方文字欄位中寫入一則訊息。") + } else { + setWallet("") + setStatus("🦊 請使用右上角的按鈕連接到 MetaMask。") + } + }) + } else { + setStatus( ++ {" "} + 🦊 + 你必須在瀏覽器中安裝 MetaMask,一個虛擬的以太坊錢包。 + +
+ ) + } +} +``` + +讓我們趕快分析在這裡發生的事情: + +- 首先,我們的函式會檢查 `window.ethereum` 是否已啟用 (即 MetaMask 已安裝)。 + - 如果沒有啟用,我們只需將 `status` 狀態變數設定為一個 JSX 字串,提示使用者安裝 MetaMask。 + - 如果已啟用,我們在第 3 行設定監聽器 `window.ethereum.on("accountsChanged")`,它會監聽 MetaMask 錢包的狀態變化,包括使用者將額外帳戶連接到去中心化應用程式、切換帳戶或中斷帳戶連線時。 如果至少有一個帳戶已連接,`walletAddress` 狀態變數會更新為監聽器回傳的 `accounts` 陣列中的第一個帳戶。 否則,`walletAddress` 會被設定為空字串。 + +最後,我們必須在 `useEffect` 函式中呼叫它: + +```javascript +useEffect(async () => { + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) + + addWalletListener() +}, []) +``` + +然後就沒有然後了! 我們已經完成了所有有關我們錢包功能的編程! 現在我們的錢包已被設定好,讓我們探索一下怎樣鑄造我們的 NFT 吧! + +## NFT 元資料入門 {#nft-metadata-101} + +那麼記得我們剛才在此教程的第零步驟提到的NFT元數據 - 它把一個虛擬的NFT帶到現實生活中,容許它持有特質,像是一個電子資產、名稱、描述,還有其他屬性。 + +我們需要將此元資料設定為一個 JSON 物件並儲存它,這樣我們就可以在呼叫智能合約的 `mintNFT` 函式時,將它作為 `tokenURI` 參數傳入。 + +在欄位的文字:「往資產的連結」、「名稱」,「描述」將會由我們NFT元數據的不同特質而組成。 我們將會把這些文字整理成為一個JSON客體,但是那裡有能讓我們能夠選擇把這個客體儲存好的幾個位置: + +- 我們可以把它安放在以太坊區塊鏈上;但是,這樣做的花費會非常昂貴。 +- 我們可以把它儲存在一個中心化服務器上,像 AWS或Firebase。 但是那樣可能會違背我們的去中央化宗旨。 +- 我們也可以使用IPFS,它是一個去中央化的規條和朋輩間的網路,讓我們在一個分配檔案系統內儲存及分享數據。 因為這個規條是去中央化和免費的,它是我們的最佳選項! + +為了將我們的元資料儲存在 IPFS 上,我們將使用 [Pinata](https://pinata.cloud/),這是一個方便的 IPFS API 和工具包。 在下一步,我們將會逐步解釋怎樣使用它! + +## 使用 Pinata 將你的元資料釘選到 IPFS {#use-pinata-to-pin-your-metadata-to-IPFS} + +如果你沒有 [Pinata](https://pinata.cloud/) 帳戶,請[在此](https://app.pinata.cloud/auth/signup)註冊一個免費帳戶,並完成驗證你的電子郵件和帳戶的步驟。 + +### 建立你的 Pinata API 金鑰 {#create-pinata-api-key} + +導覽至 [https://pinata.cloud/keys](https://pinata.cloud/keys) 頁面,然後選取頂部的「New Key」按鈕,將 Admin 小工具設定為啟用,並為你的金鑰命名。 + +你便會看見一個彈出視窗,內有你的API資訊。 確保把這個鑰匙放在某個安全的地方。 + +現在我們的鑰匙被設定好了,讓我們把它添加到專案中來使用它。 + +### 建立一個 .env 檔案 {#create-a-env} + +我們能夠在一個環境檔案中安全地儲存我們的Pinata金鑰和祕密。 讓我們在你的專案目錄中安裝 [dotenv 套件](https://www.npmjs.com/package/dotenv)。 + +在你的終端機中開啟一個新分頁 (與執行 local host 的分頁分開),並確保你在 `minter-starter-files` 資料夾中,然後在終端機中執行以下指令: + +```text +npm install dotenv --save +``` + +接下來,在你的 `minter-starter-files` 的根目錄中建立一個 `.env` 檔案,方法是在你的指令列中輸入以下內容: + +```javascript +vim.env +``` + +這會在 vim (一個文字編輯器) 中開啟你的 `.env` 檔案。 在你的鍵盤上以這個順序來點擊按鍵「esc」+「:」+「q」進行儲存。 + +接下來,在 VSCode 中,導覽至你的 `.env` 檔案,並將你的 Pinata API 金鑰和 API 密鑰加入其中,如下所示: + +```text +REACT_APP_PINATA_KEY =