---
# System prepended metadata

title: MetaCoin 範例
tags: [example, NTA-Lab, MetaCoin]

---

# 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)
:::
