# MetaCoin 範例
###### tags: `MetaCoin` `example` `NTA-Lab`
## 創建專案
1. 新增專案資料夾
```bash
$ mkdir MetaCoin
$ cd MetaCoin
```
2. 使用 Truffle 的 `unbox` 下載 MetaCoin 的 box
```bash
$ truffle unbox metacoin
```

## 啟動 Ganache 私有鏈及部署智能合約
1. 啟動 Ganache 並設定為 `7545` port
```bash
$ ganache-cli -p 7545
```

2. 編輯 `truffle-config.js` 設定
```javascript=
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
},
}
};
```
3. 編譯智能合約
```bash
$ truffle compile
```

4. 部署智能合約
```bash
$ truffle migrate
```

:::spoiler
> 智能合約內容詳見:https://github.com/truffle-box/metacoin-box/blob/master/contracts/MetaCoin.sol
:::
## 與智能合約互動
1. 進入主控台模式
```bash
$ truffle console
```

2. 定義已部署的智能合約及區塊鏈中的帳戶
```javascript
let instance = await MetaCoin.deployed()
let accounts = await web3.eth.getAccounts()
```

3. 檢查部署智能合約的帳戶的 `MetaCoin` 餘額
```javascript
let balance = await instance.getBalance(accounts[0])
balance.toNumber()
```

4. 從第一個帳號轉移 10 `MetaCoin` 至第二個帳戶
```javascript
instance.sendCoin(accounts[1], 10)
```

5. 查看接收者的餘額
```javascript
let received = await instance.getBalance(accounts[1])
received.toNumber()
```

6. 查看發送者的餘額
```javascript
let newBalance = await instance.getBalance(accounts[0])
newBalance.toNumber()
```

## 將與智能合約之互動撰寫成 `JS` 檔
1. 新增 `client` 資料夾
```bash
$ mkdir client
$ cd client
```
2. 建立 `package.json`,皆使用預設設定即可
```bash
$ npm init
```

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
```

## 以 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
```

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. 執行完後可看到三者餘額皆有變化

:::danger
如輸入的轉帳金額乘以 0.8 後不為整數,因 Solidity 不支援整數以外的數,將導致錯誤

:::