前言 本文參考 Alchemy 的範例 進行實作,Alchemy 是一個區塊鏈開發者平台,能夠讓開發者訪問以太坊區塊鏈上的 API 端點,並且可以讀寫交易。
本文採用的區塊鏈測試網路與範例文章不同,使用的是 rinkeby
測試網路。
建立專案 將 hello-world-part-four-tutorial
範例專案克隆下來,並建立一個 React 前端專案。
1 2 3 git clone https://github.com/alchemyplatform/hello-world-part-four-tutorial.git cp -R hello-world-part-four-tutorial/starter-files smart-contract-ui-examplecd smart-contract-ui-example
安裝依賴套件。
安裝 @alch/alchemy-web3
套件。
1 npm install @alch/alchemy-web3
安裝 dot-env
套件。
1 npm install dotenv --save
啟動服務。
前往 http://localhost:3000 瀏覽。
建立合約實例 新增 .env
檔。
1 REACT_APP_ALCHEMY_KEY=wss://eth-rinkeby.ws.alchemyapi.io/v2/your-api-key
更新 src/util/interact.js
檔。
1 2 3 4 5 6 require ("dotenv" ).config ();const alchemyKey = process.env .REACT_APP_ALCHEMY_KEY ;const { createAlchemyWeb3 } = require ("@alch/alchemy-web3" );const web3 = createAlchemyWeb3 (alchemyKey);
前往 Etherscan 將合約的 ABI 複製起來,更新 contract-abi.json
檔。
1 [ { "inputs" : [ { "internalType" : "string" , "name" : "initMsg" , "type" : "string" } ] , "stateMutability" : "nonpayable" , "type" : "constructor" } , { "anonymous" : false , "inputs" : [ { "indexed" : false , "internalType" : "string" , "name" : "oldStr" , "type" : "string" } , { "indexed" : false , "internalType" : "string" , "name" : "newStr" , "type" : "string" } ] , "name" : "UpdatedMessages" , "type" : "event" } , { "inputs" : [ ] , "name" : "message" , "outputs" : [ { "internalType" : "string" , "name" : "" , "type" : "string" } ] , "stateMutability" : "view" , "type" : "function" } , { "inputs" : [ { "internalType" : "string" , "name" : "newMsg" , "type" : "string" } ] , "name" : "update" , "outputs" : [ ] , "stateMutability" : "nonpayable" , "type" : "function" } ]
在 src/util/interact.js
檔中引入 ABI 並建立合約實例。
1 2 3 4 5 6 7 const contractABI = require ("../contract-abi.json" )const contractAddress = "0x6839691078Ef669589F65Fca9968f6430D509812" ;export const helloWorldContract = new web3.eth .Contract ( contractABI, contractAddress );
印出當前訊息 實作 src/util/interact.js
檔中的 loadCurrentMessage()
方法。
1 2 3 4 export const loadCurrentMessage = async ( ) => { const message = await helloWorldContract.methods .message ().call (); return message; };
更新 src/HelloWorld.js
檔中的 useEffect
方法,在一開始將當前的合約訊息印出來。
1 2 3 4 5 6 7 8 async function fetchMessage ( ) { const message = await loadCurrentMessage (); setMessage (message); } useEffect (() => { fetchMessage (); }, []);
監聽合約事件 實作 src/HelloWorld.js
檔中的 addSmartContractListener()
方法。當合約中的 UpdatedMessages
事件被觸發時,取得合約的最新訊息。
1 2 3 4 5 6 7 8 9 10 11 12 function addSmartContractListener ( ) { helloWorldContract.events .UpdatedMessages ({}, (error, data ) => { if (error) { setStatus (`😥 ${error.message} ` ); } else { const [before, after] = data.returnValues ; setMessage (after); setNewMessage ("" ); setStatus ("🎉 Your message has been updated!" ); } }); }
更新 src/HelloWorld.js
檔中的 useEffect
方法,在一開始監聽合約事件。
1 2 3 4 useEffect (() => { fetchMessage (); addSmartContractListener (); }, []);
連接錢包 實作 src/util/interact.js
檔中的 connectWallet()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 export const connectWallet = async ( ) => { if (window .ethereum ) { try { const addressArray = await window .ethereum .request ({ method : "eth_requestAccounts" , }); const obj = { status : "👆🏽 Write a message in the text-field above." , address : addressArray[0 ], }; return obj; } catch (err) { return { address : "" , status : `😥 ${err.message} ` , }; } } else { return { address : "" , status : ( <span > <p > {" "} 🦊{" "} <a target ="_blank" href ={ `https: //metamask.io /download.html `} rel ="noreferrer" > You must install Metamask, a virtual Ethereum wallet, in your browser. </a > </p > </span > ), }; } };
實作 src/HelloWorld.js
檔中的 connectWalletPressed()
方法。當按下連接錢包的按鈕時,連接錢包。
1 2 3 4 5 const connectWalletPressed = async ( ) => { const walletResponse = await connectWallet (); setStatus (walletResponse.status ); setWallet (walletResponse.address ); };
接著再實作 src/util/interact.js
檔中的 getCurrentWalletConnected()
方法,避免重新整理頁面後,錢包的連接狀態消失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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 : "👆🏽 Write a message in the text-field above." , }; } else { return { address : "" , status : "🦊 Connect to Metamask using the top right button." , }; } } catch (err) { return { address : "" , status : `😥 ${err.message} ` , }; } } else { return { address : "" , status : ( <span > <p > {" "} 🦊{" "} <a target ="_blank" href ={ `https: //metamask.io /download.html `} rel ="noreferrer" > You must install Metamask, a virtual Ethereum wallet, in your browser. </a > </p > </span > ), }; } };
更新 src/HelloWorld.js
檔中的 useEffect
方法,在一開始取得錢包資訊。
1 2 3 4 5 6 7 8 9 10 11 async function fetchWallet ( ) { const {address, status} = await getCurrentWalletConnected (); setWallet (address); setStatus (status); } useEffect (() => { fetchMessage (); addSmartContractListener (); fetchWallet (); }, []);
監聽錢包狀態 實作 src/HelloWorld.js
檔中的 addWalletListener()
方法。當錢包斷開時,更新狀態。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function addWalletListener ( ) { if (window .ethereum ) { window .ethereum .on ("accountsChanged" , (accounts ) => { if (accounts.length > 0 ) { setWallet (accounts[0 ]); setStatus ("👆🏽 Write a message in the text-field above." ); } else { setWallet ("" ); setStatus ("🦊 Connect to Metamask using the top right button." ); } }); } else { setStatus ( <p > {" "} 🦊{" "} <a target ="_blank" href ={ `https: //metamask.io /download.html `} rel ="noreferrer" > You must install Metamask, a virtual Ethereum wallet, in your browser. </a > </p > ); } }
更新 src/HelloWorld.js
檔中的 useEffect
方法,在一開始監聽錢包狀態。
1 2 3 4 5 6 useEffect (() => { fetchMessage (); addSmartContractListener (); fetchWallet (); addWalletListener (); }, []);
建立交易 實作 src/util/interact.js
檔中的 updateMessage()
方法。透過 Metamask 傳送交易請求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 export const updateMessage = async (address, message ) => { if (!window .ethereum || address === null ) { return { status : "💡 Connect your Metamask wallet to update the message on the blockchain." , }; } if (message.trim () === "" ) { return { status : "❌ Your message cannot be an empty string." , }; } const transactionParameters = { to : contractAddress, from : address, data : helloWorldContract.methods .update (message).encodeABI (), }; try { const txHash = await window .ethereum .request ({ method : "eth_sendTransaction" , params : [transactionParameters], }); return { status : ( <span > ✅{" "} <a target ="_blank" href ={ `https: //rinkeby.etherscan.io /tx /${txHash }`} rel ="noreferrer" > View the status of your transaction on Etherscan! </a > <br /> ℹ️ Once the transaction is verified by the network, the message will be updated automatically. </span > ), }; } catch (error) { return { status : `😥 ${error.message} ` , }; } };
實作 src/HelloWorld.js
檔中的 onUpdatePressed()
方法。當按下更新訊息的按鈕時,更新訊息。
1 2 3 4 const onUpdatePressed = async ( ) => { const { status } = await updateMessage (walletAddress, newMessage); setStatus (status); };
回到 UI 頁面,打上合約的新訊息,按下更新訊息的按鈕,並且完成交易。前往 Etherscan 查看,即可看到多了一筆交易。
程式碼
參考資料