# MetaCoin 範例 ###### tags: `MetaCoin` `example` `NTA-Lab` ## 創建專案 1. 新增專案資料夾 ```bash $ mkdir MetaCoin $ cd MetaCoin ``` 2. 使用 Truffle 的 `unbox` 下載 MetaCoin 的 box ```bash $ truffle unbox metacoin ``` ![](https://i.imgur.com/dszPW5D.png) ## 啟動 Ganache 私有鏈及部署智能合約 1. 啟動 Ganache 並設定為 `7545` port ```bash $ ganache-cli -p 7545 ``` ![](https://i.imgur.com/ciYAkeL.png) 2. 編輯 `truffle-config.js` 設定 ```javascript= module.exports = { networks: { development: { host: "127.0.0.1", port: 7545, network_id: "*" }, } }; ``` 3. 編譯智能合約 ```bash $ truffle compile ``` ![](https://i.imgur.com/R0bEVpJ.png) 4. 部署智能合約 ```bash $ truffle migrate ``` ![](https://i.imgur.com/rEiGZa1.png) :::spoiler > 智能合約內容詳見:https://github.com/truffle-box/metacoin-box/blob/master/contracts/MetaCoin.sol ::: ## 與智能合約互動 1. 進入主控台模式 ```bash $ truffle console ``` ![](https://i.imgur.com/IkSEC82.png) 2. 定義已部署的智能合約及區塊鏈中的帳戶 ```javascript let instance = await MetaCoin.deployed() let accounts = await web3.eth.getAccounts() ``` ![](https://i.imgur.com/4IhCpnj.png) 3. 檢查部署智能合約的帳戶的 `MetaCoin` 餘額 ```javascript let balance = await instance.getBalance(accounts[0]) balance.toNumber() ``` ![](https://i.imgur.com/OZNLJW1.png) 4. 從第一個帳號轉移 10 `MetaCoin` 至第二個帳戶 ```javascript instance.sendCoin(accounts[1], 10) ``` ![](https://i.imgur.com/HR0CFy5.png) 5. 查看接收者的餘額 ```javascript let received = await instance.getBalance(accounts[1]) received.toNumber() ``` ![](https://i.imgur.com/LJZzir0.png) 6. 查看發送者的餘額 ```javascript let newBalance = await instance.getBalance(accounts[0]) newBalance.toNumber() ``` ![](https://i.imgur.com/SDNOZC1.png) ## 將與智能合約之互動撰寫成 `JS` 檔 1. 新增 `client` 資料夾 ```bash $ mkdir client $ cd client ``` 2. 建立 `package.json`,皆使用預設設定即可 ```bash $ npm init ``` ![](https://i.imgur.com/Oc8v1W3.png) 3. 下載 `web3.js` 套件 ```bash $ npm install web3 ``` 4. 編輯 `index.js` ```bash $ nano index.js ``` ```javascript= const MetaCoin = require('../build/contracts/MetaCoin.json'); const Web3 = require('web3'); main = async() => { // 設定區塊鏈連線位置 const url = 'http://127.0.0.1:7545'; // 使用 web3.js 連線至區塊鏈 const web3 = new Web3(url); // 取得區塊鏈中的帳戶 const accounts = await web3.eth.getAccounts(); // 取得智能合約於區塊鏈網路中相關資訊 const networkId = await web3.eth.net.getId(); const deployedNetwork = MetaCoin.networks[networkId]; const instance = new web3.eth.Contract( MetaCoin.abi, deployedNetwork && deployedNetwork.address ); // 開始與智能合約互動 // [+] 情境: 假設部署智能合約者為消費者, // 第二個帳戶為零售商,第三個帳戶為製造廠商, // 消費者向零售商以 10 MetaCoin 購買商品, // 零售商以 8 MetaCoin 向製造廠商訂貨, // 並賺取其中 2 MetaCoin 的價差。 console.log('\n=== 1. 定義第一個帳戶為消費者,第二個帳戶為零售商,第三個帳戶為製造廠商 ===\n'); var consumer = accounts[0]; var retailer = accounts[1]; var manufacturer = accounts[2]; console.log('\tConsumer: ', consumer); console.log('\tRetailer: ', retailer); console.log('\tManufacturer: ', manufacturer); console.log('\n=== 2. 消費者付 10 MetaCoin 給零售商 ===\n'); await instance.methods.sendCoin(retailer, 10).send({from: consumer}); console.log('\n=== 3. 查看消費者及零售商餘額 ===\n'); var consumer_balance = await instance.methods.getBalance(consumer).call(); var retailer_balance = await instance.methods.getBalance(retailer).call(); console.log('\tConsumer Balance: ', consumer_balance); console.log('\tRetailer Balance: ', retailer_balance); console.log('\n=== 4. 零售商付 8 MetaCoin 給製造商 ===\n'); await instance.methods.sendCoin(manufacturer, 8).send({from: retailer}); console.log('\n=== 5. 查看消零售商及製造商餘額 ===\n'); var retailer_new_balance = await instance.methods.getBalance(retailer).call(); var manufacturer_balance = await instance.methods.getBalance(manufacturer).call(); console.log('\tRetailer New Balance: ', retailer_new_balance); console.log('\tManufacturer Balance: ', manufacturer_balance); } main(); ``` 5. 執行 `index.js` ```bash $ node index.js ``` ![](https://i.imgur.com/ksefHIu.png) ## 以 Docker 方式將消費者、零售商、製造商三個角色分開 1. 新增並編輯 `docker-compose.yml` 檔 ```bash $ nano docker-compose.yml ``` ```dockerfile= version: '2' services: consumer: container_name: consumer image: node:latest ports: - "8080:8080" working_dir: /scripts volumes: - ./build/:/scripts/build - ./client/:/scripts stdin_open: true tty: true network_mode: "host" retailer: container_name: retailer image: node:latest ports: - "8081:8081" working_dir: /scripts volumes: - ./build/:/scripts/build - ./client/:/scripts stdin_open: true tty: true network_mode: "host" manufacturer: container_name: manufacturer image: node:latest ports: - "8082:8082" working_dir: /scripts volumes: - ./build/:/scripts/build - ./client/:/scripts stdin_open: true tty: true network_mode: "host" ``` 2. 編輯 `./client/docker_node.js` 檔 ```javascript= const MetaCoin = require('./build/contracts/MetaCoin.json'); const Web3 = require('web3'); var argv = require('minimist')(process.argv.slice(2)); main = async() => { // 改用 WebSocket 協定,只需完成一次交握就能建立持久性的連接,能夠持續監聽智能合約中的 event const url = 'ws://127.0.0.1:7545'; const web3 = new Web3(url); const accounts = await web3.eth.getAccounts(); const networkId = await web3.eth.net.getId(); const deployedNetwork = MetaCoin.networks[networkId]; const instance = new web3.eth.Contract( MetaCoin.abi, deployedNetwork && deployedNetwork.address ); var consumer_account = accounts[0]; var retailer_account = accounts[1]; var manufacturer_account = accounts[2]; var role = argv['role']; switch (role) { case 'c': await send(role, consumer_account, retailer_account, argv['amount'], instance); process.exit(1); break; default: instance.events.Transfer({}).on('data', event => { transferFrom = event.returnValues._from; transferTo = event.returnValues._to; transferValue = event.returnValues._value; if (argv['role'] == 'r') { if (transferTo == retailer_account) { send(role, retailer_account, manufacturer_account, transferValue * 0.8, instance); } }else if (argv['role'] == 'm') { if (transferTo == manufacturer_account) { getResult(role, manufacturer_account, instance); } } }) break; } } send = async(sender, account, receiver, amount, contract) => { console.log('\n========== Send Meta Coin =========='); if (sender == 'c') { console.log('From: Consumer \tTo: Retailer'); } else { console.log('From: Retailer \tTo: Manufacturer'); } await contract.methods.sendCoin(receiver, amount).send({from: account}); await getResult(sender, account, contract); } getResult = async(role, account, contract) => { var balance = await contract.methods.getBalance(account).call(); switch (role) { case 'c': console.log('\tConsumer Balance: ', balance) break; case 'r': console.log('\tRetailer Balance: ', balance) break; case 'm': console.log('\tManufacturer Balance: ', balance) break; } } main(); ``` 3. 啟動 `container` ```bash $ docker-compose up -d ``` ![](https://i.imgur.com/62cLhrF.png) 4. 開啟三個 terminal 分別進入消費者、零售商、製造商 container ```bash $ docker exec -it consumer bash $ docker exec -it retailer bash $ docker exec -it manufacturer bash ``` 5. 在製造商視窗執行 `docker_node.js` 並設定參數 `--role m` ```bash $ node docker_node.js --role m ``` 6. 接著在零售商視窗執行 `docker_node.js` 並設定參數 `--role r` ```bash $ node docker_node.js --role r ``` 7. 最後於消費者視窗執行 `docker_node.js` 並設定參數 `--role c`,並再加上轉帳參數 `--amount $NUMBER`,以下以轉帳 10 MetaCoin為例 ```bash $ node docker_node.js --role c --amount 10 ``` 8. 執行完後可看到三者餘額皆有變化 ![](https://i.imgur.com/vU5Vdds.png) :::danger 如輸入的轉帳金額乘以 0.8 後不為整數,因 Solidity 不支援整數以外的數,將導致錯誤 ![](https://i.imgur.com/JzTHxGW.png) :::