├── .gitignore
├── bs-config.json
├── uml
├── Data Modelling.jpeg
├── State Diagram.jpeg
├── Activity Diagram.jpeg
└── Sequence Diagram.jpeg
├── migrations
├── 1_initial_migration.js
└── 2_deploy_contracts.js
├── package.json
├── contracts
├── Migrations.sol
├── coffeeaccesscontrol
│ ├── Roles.sol
│ ├── FarmerRole.sol
│ ├── ConsumerRole.sol
│ ├── RetailerRole.sol
│ └── DistributorRole.sol
├── coffeecore
│ └── Ownable.sol
└── coffeebase
│ └── SupplyChain.sol
├── website
├── css
│ └── style.css
├── index.html
└── js
│ └── app.js
├── truffle-config.js
├── README.md
└── test
└── TestSupplychain.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | .idea
4 | .secret
5 |
--------------------------------------------------------------------------------
/bs-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "baseDir": ["./website", "./build/contracts"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/uml/Data Modelling.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ireade/nd1309-supply-chain/HEAD/uml/Data Modelling.jpeg
--------------------------------------------------------------------------------
/uml/State Diagram.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ireade/nd1309-supply-chain/HEAD/uml/State Diagram.jpeg
--------------------------------------------------------------------------------
/uml/Activity Diagram.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ireade/nd1309-supply-chain/HEAD/uml/Activity Diagram.jpeg
--------------------------------------------------------------------------------
/uml/Sequence Diagram.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ireade/nd1309-supply-chain/HEAD/uml/Sequence Diagram.jpeg
--------------------------------------------------------------------------------
/migrations/1_initial_migration.js:
--------------------------------------------------------------------------------
1 | var Migrations = artifacts.require("./Migrations.sol");
2 |
3 | module.exports = function(deployer) {
4 | deployer.deploy(Migrations);
5 | };
6 |
--------------------------------------------------------------------------------
/migrations/2_deploy_contracts.js:
--------------------------------------------------------------------------------
1 | const FarmerRole = artifacts.require("./FarmerRole.sol");
2 | const DistributorRole = artifacts.require("./DistributorRole.sol");
3 | const RetailerRole = artifacts.require("./RetailerRole.sol");
4 | const ConsumerRole = artifacts.require("./ConsumerRole.sol");
5 | const SupplyChain = artifacts.require("./SupplyChain.sol");
6 |
7 | module.exports = function (deployer) {
8 | deployer.deploy(FarmerRole);
9 | deployer.deploy(DistributorRole);
10 | deployer.deploy(RetailerRole);
11 | deployer.deploy(ConsumerRole);
12 | deployer.deploy(SupplyChain);
13 | };
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fair-trade-coffee-supply-chain",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "develop": "truffle develop",
8 | "dev": "lite-server",
9 | "compile": "truffle compile",
10 | "migrate": "truffle migrate --reset",
11 | "test": "truffle test",
12 | "deploy": "truffle migrate --reset --network rinkeby",
13 | "host": "ipfs add -r website"
14 | },
15 | "keywords": [],
16 | "license": "MIT",
17 | "devDependencies": {
18 | "lite-server": "2.4.0"
19 | },
20 | "dependencies": {
21 | "truffle-hdwallet-provider": "^1.0.16"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | contract Migrations {
4 | address public owner;
5 | uint public last_completed_migration;
6 |
7 | constructor() public {
8 | owner = msg.sender;
9 | }
10 |
11 | modifier restricted() {
12 | if (msg.sender == owner) _;
13 | }
14 |
15 | function setCompleted(uint completed) public restricted {
16 | last_completed_migration = completed;
17 | }
18 |
19 | function upgrade(address new_address) public restricted {
20 | Migrations upgraded = Migrations(new_address);
21 | upgraded.setCompleted(last_completed_migration);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/coffeeaccesscontrol/Roles.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | /**
4 | * @title Roles
5 | * @dev Library for managing addresses assigned to a Role.
6 | */
7 | library Roles {
8 | struct Role {
9 | mapping(address => bool) bearer;
10 | }
11 |
12 | /**
13 | * @dev give an account access to this role
14 | */
15 | function add(Role storage role, address account) internal {
16 | require(account != address(0));
17 | require(!has(role, account));
18 |
19 | role.bearer[account] = true;
20 | }
21 |
22 | /**
23 | * @dev remove an account's access to this role
24 | */
25 | function remove(Role storage role, address account) internal {
26 | require(account != address(0));
27 | require(has(role, account));
28 |
29 | role.bearer[account] = false;
30 | }
31 |
32 | /**
33 | * @dev check if an account has this role
34 | * @return bool
35 | */
36 | function has(Role storage role, address account)
37 | internal
38 | view
39 | returns (bool)
40 | {
41 | require(account != address(0));
42 | return role.bearer[account];
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/contracts/coffeeaccesscontrol/FarmerRole.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "./Roles.sol";
4 |
5 | contract FarmerRole {
6 | using Roles for Roles.Role;
7 |
8 | event FarmerAdded(address indexed account);
9 | event FarmerRemoved(address indexed account);
10 |
11 | Roles.Role private farmers;
12 |
13 | constructor() public {
14 | _addFarmer(msg.sender);
15 | }
16 |
17 | modifier onlyFarmer() {
18 | require(isFarmer(msg.sender), "Only Farmer allowed to perform this operation");
19 | _;
20 | }
21 |
22 | function isFarmer(address account) public view returns (bool) {
23 | return farmers.has(account);
24 | }
25 |
26 | function addFarmer(address account) public onlyFarmer {
27 | _addFarmer(account);
28 | }
29 |
30 | function renounceFarmer() public {
31 | _removeFarmer(msg.sender);
32 | }
33 |
34 | function _addFarmer(address account) internal {
35 | farmers.add(account);
36 | emit FarmerAdded(account);
37 | }
38 |
39 | function _removeFarmer(address account) internal {
40 | farmers.remove(account);
41 | emit FarmerRemoved(account);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/coffeeaccesscontrol/ConsumerRole.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "./Roles.sol";
4 |
5 | contract ConsumerRole {
6 | using Roles for Roles.Role;
7 |
8 | event ConsumerAdded(address indexed account);
9 | event ConsumerRemoved(address indexed account);
10 |
11 | Roles.Role private consumers;
12 |
13 | constructor() public {
14 | _addConsumer(msg.sender);
15 | }
16 |
17 | modifier onlyConsumer() {
18 | require(isConsumer(msg.sender), "Only Consumer allowed to perform this operation");
19 | _;
20 | }
21 |
22 | function isConsumer(address account) public view returns (bool) {
23 | return consumers.has(account);
24 | }
25 |
26 | function addConsumer(address account) public onlyConsumer {
27 | _addConsumer(account);
28 | }
29 |
30 | function renounceConsumer() public {
31 | _removeConsumer(msg.sender);
32 | }
33 |
34 | function _addConsumer(address account) internal {
35 | consumers.add(account);
36 | emit ConsumerAdded(account);
37 | }
38 |
39 | function _removeConsumer(address account) internal {
40 | consumers.remove(account);
41 | emit ConsumerRemoved(account);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/coffeeaccesscontrol/RetailerRole.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "./Roles.sol";
4 |
5 | contract RetailerRole {
6 | using Roles for Roles.Role;
7 |
8 | event RetailerAdded(address indexed account);
9 | event RetailerRemoved(address indexed account);
10 |
11 | Roles.Role private retailers;
12 |
13 | constructor() public {
14 | _addRetailer(msg.sender);
15 | }
16 |
17 | modifier onlyRetailer() {
18 | require(isRetailer(msg.sender), "Only Retailer allowed to perform this operation");
19 | _;
20 | }
21 |
22 | function isRetailer(address account) public view returns (bool) {
23 | return retailers.has(account);
24 | }
25 |
26 | function addRetailer(address account) public onlyRetailer {
27 | _addRetailer(account);
28 | }
29 |
30 | function renounceRetailer() public {
31 | _removeRetailer(msg.sender);
32 | }
33 |
34 | function _addRetailer(address account) internal {
35 | retailers.add(account);
36 | emit RetailerAdded(account);
37 | }
38 |
39 | function _removeRetailer(address account) internal {
40 | retailers.remove(account);
41 | emit RetailerRemoved(account);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/coffeecore/Ownable.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "../coffeebase/SupplyChain.sol";
4 |
5 | contract Ownable is SupplyChain {
6 | address private origOwner;
7 |
8 | event TransferOwnership(address indexed oldOwner, address indexed newOwner);
9 |
10 | constructor () internal {
11 | origOwner = msg.sender;
12 | emit TransferOwnership(address(0), origOwner);
13 | }
14 |
15 | function owner() public view returns (address) {
16 | return origOwner;
17 | }
18 |
19 | modifier onlyOwner() {
20 | require(isOwner());
21 | _;
22 | }
23 |
24 | function isOwner() public view returns (bool) {
25 | return msg.sender == origOwner;
26 | }
27 |
28 | function renounceOwnership() public onlyOwner {
29 | emit TransferOwnership(origOwner, address(0));
30 | origOwner = address(0);
31 | }
32 |
33 | function transferOwnership(address newOwner) public onlyOwner {
34 | _transferOwnership(newOwner);
35 | }
36 |
37 | function _transferOwnership(address newOwner) internal {
38 | require(newOwner != address(0));
39 | emit TransferOwnership(origOwner, newOwner);
40 | origOwner = newOwner;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/website/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color:#fff;
3 | padding: 2em;
4 | font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif';
5 | color: #212121;
6 | }
7 | .container {
8 | width: 80%;
9 | margin: 0 auto;
10 | }
11 |
12 |
13 | .bold {
14 | font-weight: bolder;
15 | }
16 |
17 | input {
18 | width: 80%;
19 | padding: 12px 20px;
20 | margin: 10px 0;
21 | }
22 |
23 | button {
24 | cursor: pointer;
25 | background-color: #4CAF50;
26 | border: none;
27 | color: white;
28 | background-color: #00BCD4;
29 | font-size: 16px;
30 | padding: 16px 32px;
31 | text-decoration: none;
32 | margin: 4px 2px;
33 | cursor: pointer;
34 | }
35 |
36 | .sell {
37 | background-color: white;
38 | color: #D1C4E9;
39 | }
40 |
41 | .ftc-intro {
42 | background-color:#0097A7;
43 | color:white;
44 | width:100%;
45 | padding:20px;
46 | }
47 |
48 | .form-group {
49 | background-color:#0097A7;
50 | color:black;
51 | width:50%;
52 | padding:30px;
53 | border-radius: 4px;
54 | background-color: #f8f8f8;
55 | }
56 |
57 | .form-break {
58 | background-color:#fff;
59 | color:white;
60 | width:100%;
61 | padding:5px;
62 | }
63 |
64 | .horizontal-rule {
65 | background-color:#BDBDBD;
66 | }
67 |
--------------------------------------------------------------------------------
/contracts/coffeeaccesscontrol/DistributorRole.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "./Roles.sol";
4 |
5 | contract DistributorRole {
6 | using Roles for Roles.Role;
7 |
8 | event DistributorAdded(address indexed account);
9 | event DistributorRemoved(address indexed account);
10 |
11 | Roles.Role private distributors;
12 |
13 | constructor() public {
14 | _addDistributor(msg.sender);
15 | }
16 |
17 | modifier onlyDistributor() {
18 | require(isDistributor(msg.sender), "Only Distributor allowed to perform this operation");
19 | _;
20 | }
21 |
22 | function isDistributor(address account) public view returns (bool) {
23 | return distributors.has(account);
24 | }
25 |
26 | function addDistributor(address account) public onlyDistributor {
27 | _addDistributor(account);
28 | }
29 |
30 | function renounceDistributor() public {
31 | _removeDistributor(msg.sender);
32 | }
33 |
34 | function _addDistributor(address account) internal {
35 | distributors.add(account);
36 | emit DistributorAdded(account);
37 | }
38 |
39 | function _removeDistributor(address account) internal {
40 | distributors.remove(account);
41 | emit DistributorRemoved(account);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/website/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Fair Trade Coffee
7 |
8 |
9 |
10 |
11 |
12 |
Fair Trade Coffee
13 |
14 |
Prove the authenticity of coffee using the Ethereum blockchain.
15 |
16 |
17 |
90 |
91 | Transaction History
92 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/truffle-config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Use this file to configure your truffle project. It's seeded with some
3 | * common settings for different networks and features like migrations,
4 | * compilation and testing. Uncomment the ones you need or modify
5 | * them to suit your project as necessary.
6 | *
7 | * More information about configuration can be found at:
8 | *
9 | * truffleframework.com/docs/advanced/configuration
10 | *
11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider)
12 | * to sign your transactions before they're sent to a remote public node. Infura API
13 | * keys are available for free at: infura.io/register
14 | *
15 | * > > Using Truffle V5 or later? Make sure you install the `web3-one` version.
16 | *
17 | * > > $ npm install truffle-hdwallet-provider@web3-one
18 | *
19 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
20 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this
21 | * phrase from a file you've .gitignored so it doesn't accidentally become public.
22 | *
23 | */
24 |
25 | const HDWalletProvider = require('truffle-hdwallet-provider');
26 | const infuraKey = "31e6d757069c4230b6a1467c32294123";
27 |
28 | const fs = require('fs');
29 | const mnemonic = fs.readFileSync(".secret").toString().trim();
30 |
31 | module.exports = {
32 | /**
33 | * Networks define how you connect to your ethereum client and let you set the
34 | * defaults web3 uses to send transactions. If you don't specify one truffle
35 | * will spin up a development blockchain for you on port 9545 when you
36 | * run `develop` or `test`. You can ask a truffle command to use a specific
37 | * network from the command line, e.g
38 | *
39 | * $ truffle test --network
40 | */
41 |
42 | networks: {
43 | // Useful for testing. The `development` name is special - truffle uses it by default
44 | // if it's defined here and no other network is specified at the command line.
45 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal
46 | // tab if you use this network and you must also set the `host`, `port` and `network_id`
47 | // options below to some value.
48 | //
49 | development: {
50 | host: "127.0.0.1", // Localhost (default: none)
51 | port: 9545, // Standard Ethereum port (default: none)
52 | network_id: "*", // Any network (default: none)
53 | },
54 | rinkeby: {
55 | provider: () => new HDWalletProvider(mnemonic, `https://rinkeby.infura.io/v3/${infuraKey}`),
56 | network_id: 4, // rinkeby's id
57 | gas: 4500000, // rinkeby has a lower block limit than mainnet
58 | gasPrice: 10000000000
59 | },
60 |
61 | // Another network with more advanced options...
62 | // advanced: {
63 | // port: 8777, // Custom port
64 | // network_id: 1342, // Custom network
65 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000)
66 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
67 | // from: , // Account to send txs from (default: accounts[0])
68 | // websockets: true // Enable EventEmitter interface for web3 (default: false)
69 | // },
70 |
71 | // Useful for deploying to a public network.
72 | // NB: It's important to wrap the provider as a function.
73 | // ropsten: {
74 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/${infuraKey}`),
75 | // network_id: 3, // Ropsten's id
76 | // gas: 5500000, // Ropsten has a lower block limit than mainnet
77 | // confirmations: 2, // # of confs to wait between deployments. (default: 0)
78 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
79 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
80 | // },
81 |
82 | // Useful for private networks
83 | // private: {
84 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
85 | // network_id: 2111, // This network is yours, in the cloud.
86 | // production: true // Treats this network as if it was a public net. (default: false)
87 | // }
88 | },
89 |
90 | // Set default mocha options here, use special reporters etc.
91 | mocha: {
92 | // timeout: 100000
93 | },
94 |
95 | // Configure your compilers
96 | compilers: {
97 | solc: {
98 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
99 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
100 | // settings: { // See the solidity docs for advice about optimization and evmVersion
101 | // optimizer: {
102 | // enabled: false,
103 | // runs: 200
104 | // },
105 | // evmVersion: "byzantium"
106 | // }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fair Trade Coffee Supply Chain
2 |
3 | Prove the authenticity of coffee using the Ethereum blockchain.
4 |
5 | ## Project Write-Up
6 |
7 | ### UML Diagrams
8 |
9 | [Activity Diagram](./uml/Activity%20Diagram.jpeg)
10 | [Sequence Diagram](./uml/Sequence%20Diagram.jpeg)
11 | [State Diagram](./uml/State%20Diagram.jpeg)
12 | [Data Modelling](./uml/Data%20Modelling.jpeg)
13 |
14 | ### Libraries
15 |
16 | **Truffle**: Used for developing, testing, and deploying my smart contracts
17 |
18 | ### IPFS Hosting
19 |
20 | Frontend Application hosted on IPFS at [/ipfs/QmTwE6W4tcG62rD5XhmGet5kCv9V9xSrRgLnzLaQPXKJUr](https://gateway.ipfs.io/ipfs/QmTwE6W4tcG62rD5XhmGet5kCv9V9xSrRgLnzLaQPXKJUr/);
21 |
22 | ```
23 | added QmRXgpSSdL5vpvepVqJ8KAQw3vMgiJiezgUcothzqXzWam website/css/style.css
24 | added QmQJ2GBjWx17NYDRvSNXpJptkBWbpbTTryCrD9pkxEqXDn website/index.html
25 | added QmQVyzFxc7Gozfbh1iYrNDBdNfaCuJytzfV4wpZk6fK9ry website/js/app.js
26 | added QmW5qd5uHjzK5JnEkXJ5LzpYaNTna2xY4k5myZKF9hUzJL website/js/truffle-contract.js
27 | added Qmduc63Ld5riL4oBZKXZwJG5tSyMUpWdnNp6EL9kHvmxw8 website/css
28 | added QmXQju5yGTRXfGWXBYbm63gfZfuA7FGHwyHFxwKfhSWMiJ website/js
29 | added QmTwE6W4tcG62rD5XhmGet5kCv9V9xSrRgLnzLaQPXKJUr website
30 |
31 | Published to QmfBa6iKKAg5xzNDG4Go2aAZHVMSnkcXzcMcGdvWBTXQ2H: /ipfs/QmTwE6W4tcG62rD5XhmGet5kCv9V9xSrRgLnzLaQPXKJUr
32 | ```
33 |
34 |
35 | ## Smart Contract
36 |
37 | SupplyChain Tx hash: [0x7df1148cb8e1e463efffb2530d214153e9c5a41e1c4638c1aa96be99de7b969a](https://rinkeby.etherscan.io/tx/0x7df1148cb8e1e463efffb2530d214153e9c5a41e1c4638c1aa96be99de7b969a)
38 | SupplyChain Contract address: [0x7EDA0FdA90C8689B5E1d37DC8d94cD4F8344bAbB](https://rinkeby.etherscan.io/address/0x7eda0fda90c8689b5e1d37dc8d94cd4f8344babb)
39 |
40 | ### Full Migrations Output
41 |
42 | ```
43 | 1_initial_migration.js
44 | ======================
45 |
46 | Deploying 'Migrations'
47 | ----------------------
48 | > transaction hash: 0x793d089e91cf26ba5a5755d9ddf66bf641cd5f5128cb9e61e5712ad1595bcc64
49 | > Blocks: 1 Seconds: 17
50 | > contract address: 0x1e9067e5998B6ce80807a4D047A5d874e399171E
51 | > block number: 4949894
52 | > block timestamp: 1566378437
53 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
54 | > balance: 18.7017016189999999
55 | > gas used: 261265
56 | > gas price: 10 gwei
57 | > value sent: 0 ETH
58 | > total cost: 0.00261265 ETH
59 |
60 |
61 | > Saving migration to chain.
62 | > Saving artifacts
63 | -------------------------------------
64 | > Total cost: 0.00261265 ETH
65 |
66 |
67 | 2_deploy_contracts.js
68 | =====================
69 |
70 | Deploying 'FarmerRole'
71 | ----------------------
72 | > transaction hash: 0xee2f37fa88f5c48eb259ad53a3228dab6b1c34f2576580d5d84c5595d26b1bbd
73 | > Blocks: 1 Seconds: 25
74 | > contract address: 0x49f9eC69d4949d56704C8b4305c727A1310434f7
75 | > block number: 4949897
76 | > block timestamp: 1566378482
77 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
78 | > balance: 18.6972078489999999
79 | > gas used: 407354
80 | > gas price: 10 gwei
81 | > value sent: 0 ETH
82 | > total cost: 0.00407354 ETH
83 |
84 |
85 | Deploying 'DistributorRole'
86 | ---------------------------
87 | > transaction hash: 0x582732feead3925bebd944c4e79f679bde57131b132cc30dc24ff4ebee2997d4
88 | > Blocks: 0 Seconds: 5
89 | > contract address: 0xCA6BB95AC56c4E3B35407c045a36e9Bd53e0934f
90 | > block number: 4949898
91 | > block timestamp: 1566378497
92 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
93 | > balance: 18.6931215489999999
94 | > gas used: 408630
95 | > gas price: 10 gwei
96 | > value sent: 0 ETH
97 | > total cost: 0.0040863 ETH
98 |
99 |
100 | Deploying 'RetailerRole'
101 | ------------------------
102 | > transaction hash: 0xbcdb8593a37e383c89f6c495aaf704e0c4dd846581ab5becf357c0a09915b4a1
103 | > Blocks: 0 Seconds: 9
104 | > contract address: 0xBd0b3E7dDAC211dCa34B6FDff7b7CDA51FeE8381
105 | > block number: 4949899
106 | > block timestamp: 1566378512
107 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
108 | > balance: 18.6890426489999999
109 | > gas used: 407890
110 | > gas price: 10 gwei
111 | > value sent: 0 ETH
112 | > total cost: 0.0040789 ETH
113 |
114 |
115 | Deploying 'ConsumerRole'
116 | ------------------------
117 | > transaction hash: 0x0c5e3351c39d3ff12d62ecf299e7c707049a95e79fd065474673f0dd431776df
118 | > Blocks: 1 Seconds: 9
119 | > contract address: 0xb5610b67EcaDC1C96bb913642745fe2c67C8554E
120 | > block number: 4949900
121 | > block timestamp: 1566378527
122 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
123 | > balance: 18.6849637489999999
124 | > gas used: 407890
125 | > gas price: 10 gwei
126 | > value sent: 0 ETH
127 | > total cost: 0.0040789 ETH
128 |
129 |
130 | Deploying 'SupplyChain'
131 | -----------------------
132 | > transaction hash: 0x7df1148cb8e1e463efffb2530d214153e9c5a41e1c4638c1aa96be99de7b969a
133 | > Blocks: 0 Seconds: 5
134 | > contract address: 0x7EDA0FdA90C8689B5E1d37DC8d94cD4F8344bAbB
135 | > block number: 4949901
136 | > block timestamp: 1566378542
137 | > account: 0xb43eAdc52571fD08291FA783AEa561187d2C544D
138 | > balance: 18.6510206889999999
139 | > gas used: 3394306
140 | > gas price: 10 gwei
141 | > value sent: 0 ETH
142 | > total cost: 0.03394306 ETH
143 |
144 |
145 | > Saving migration to chain.
146 | > Saving artifacts
147 | -------------------------------------
148 | > Total cost: 0.0502607 ETH
149 |
150 |
151 | Summary
152 | =======
153 | > Total deployments: 6
154 | > Final cost: 0.05287335 ETH
155 | ```
156 |
157 | ## Development Notes
158 |
159 | Truffle v5.0.31 (core: 5.0.31)
160 | Solidity v0.5.0 (solc-js)
161 | Node v10.16.0
162 | Web3.js v1.2.1
163 |
164 |
--------------------------------------------------------------------------------
/test/TestSupplychain.js:
--------------------------------------------------------------------------------
1 | const SupplyChain = artifacts.require("SupplyChain");
2 |
3 | let accounts;
4 | let supplyChain;
5 |
6 | const emptyAddress = "0x00000000000000000000000000000000000000";
7 |
8 | let sku = 1;
9 | let upc = 1;
10 | let ownerID = emptyAddress;
11 | let originFarmerID = emptyAddress;
12 | const originFarmName = "Rey Farms";
13 | const originFarmInformation = "Lagos";
14 | const originFarmLatitude = "6.451140";
15 | const originFarmLongitude = "3.388400";
16 | let productID = sku + upc;
17 | const productNotes = "Best beans for Espresso";
18 | const productPrice = web3.utils.toWei("1", "ether");
19 | let itemState = 0;
20 | let distributorID = emptyAddress;
21 | let retailerID = emptyAddress;
22 | let consumerID = emptyAddress;
23 |
24 | contract("SupplyChain", function (acc) {
25 | accounts = acc;
26 |
27 | ownerID = accounts[0];
28 | originFarmerID = accounts[1];
29 | distributorID = accounts[2];
30 | retailerID = accounts[3];
31 | consumerID = accounts[4];
32 |
33 | console.log("Contract Owner: accounts[0] ", ownerID);
34 | console.log("Farmer: accounts[1] ", originFarmerID);
35 | console.log("Distributor: accounts[2] ", distributorID);
36 | console.log("Retailer: accounts[3] ", retailerID);
37 | console.log("Consumer: accounts[4] ", consumerID);
38 | });
39 |
40 | beforeEach(async () => {
41 | supplyChain = await SupplyChain.deployed();
42 | });
43 |
44 |
45 | it("Testing smart contract function harvestItem() that allows a farmer to harvest coffee", async () => {
46 |
47 | await supplyChain.addFarmer(originFarmerID);
48 |
49 | let eventEmitted = false;
50 | await supplyChain.Harvested((err, res) => eventEmitted = true);
51 |
52 | await supplyChain.harvestItem(
53 | upc,
54 | originFarmerID,
55 | originFarmName,
56 | originFarmInformation,
57 | originFarmLatitude,
58 | originFarmLongitude,
59 | productNotes,
60 | { from: originFarmerID }
61 | );
62 | itemState = 0;
63 |
64 | const resultBufferOne = await supplyChain.fetchItemBufferOne.call(upc);
65 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
66 |
67 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU');
68 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC');
69 | assert.equal(resultBufferOne[2], ownerID, 'Error: Missing or Invalid ownerID');
70 | assert.equal(resultBufferOne[3], originFarmerID, 'Error: Missing or Invalid originFarmerID');
71 | assert.equal(resultBufferOne[4], originFarmName, 'Error: Missing or Invalid originFarmName');
72 | assert.equal(resultBufferOne[5], originFarmInformation, 'Error: Missing or Invalid originFarmInformation');
73 | assert.equal(resultBufferOne[6], originFarmLatitude, 'Error: Missing or Invalid originFarmLatitude');
74 | assert.equal(resultBufferOne[7], originFarmLongitude, 'Error: Missing or Invalid originFarmLongitude');
75 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
76 | assert.equal(eventEmitted, true, 'Error: Harvested event not emitted');
77 | });
78 |
79 |
80 | it("Testing smart contract function processItem() that allows a farmer to process coffee", async () => {
81 |
82 | let eventEmitted = false;
83 | await supplyChain.Processed((err, res) => eventEmitted = true);
84 |
85 | await supplyChain.processItem(upc, { from: originFarmerID });
86 | itemState = 1;
87 |
88 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
89 |
90 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
91 | assert.equal(eventEmitted, true, 'Error: Harvested event not emitted');
92 | });
93 |
94 |
95 | it("Testing smart contract function packItem() that allows a farmer to pack coffee", async () => {
96 |
97 | let eventEmitted = false;
98 | await supplyChain.Packed((err, res) => eventEmitted = true);
99 |
100 | await supplyChain.packItem(upc, { from: originFarmerID });
101 | itemState = 2;
102 |
103 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
104 |
105 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
106 | assert.equal(eventEmitted, true, 'Error: Packed event not emitted');
107 | });
108 |
109 |
110 | it("Testing smart contract function sellItem() that allows a farmer to sell coffee", async () => {
111 |
112 | let eventEmitted = false;
113 | await supplyChain.ForSale((err, res) => eventEmitted = true);
114 |
115 | await supplyChain.sellItem(upc, productPrice, { from: originFarmerID });
116 | itemState = 3;
117 |
118 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
119 |
120 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Invalid item productPrice');
121 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
122 | assert.equal(eventEmitted, true, 'Error: ForSale event not emitted');
123 | });
124 |
125 |
126 | it("Testing smart contract function buyItem() that allows a distributor to buy coffee", async () => {
127 |
128 | await supplyChain.addDistributor(distributorID);
129 |
130 | let eventEmitted = false;
131 | await supplyChain.Sold((err, res) => eventEmitted = true);
132 |
133 | await supplyChain.buyItem(upc, { from: distributorID, value: productPrice });
134 | itemState = 4;
135 |
136 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
137 |
138 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid distributorID');
139 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
140 | assert.equal(eventEmitted, true, 'Error: ForSale event not emitted');
141 |
142 | });
143 |
144 |
145 | it("Testing smart contract function shipItem() that allows a distributor to ship coffee", async () => {
146 |
147 | let eventEmitted = false;
148 | await supplyChain.Shipped((err, res) => eventEmitted = true);
149 |
150 | await supplyChain.shipItem(upc, { from: distributorID });
151 | itemState = 5;
152 |
153 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
154 |
155 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
156 | assert.equal(eventEmitted, true, 'Error: Shipped event not emitted');
157 | });
158 |
159 |
160 | it("Testing smart contract function receiveItem() that allows a retailer to mark coffee received", async () => {
161 |
162 | await supplyChain.addRetailer(retailerID);
163 |
164 | let eventEmitted = false;
165 | await supplyChain.Received((err, res) => eventEmitted = true);
166 |
167 | await supplyChain.receiveItem(upc, { from: retailerID });
168 | itemState = 6;
169 |
170 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
171 |
172 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid distributorID');
173 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
174 | assert.equal(eventEmitted, true, 'Error: Received event not emitted');
175 | });
176 |
177 |
178 | it("Testing smart contract function purchaseItem() that allows a consumer to purchase coffee", async () => {
179 |
180 | await supplyChain.addConsumer(consumerID);
181 |
182 | let eventEmitted = false;
183 | await supplyChain.Purchased((err, res) => eventEmitted = true);
184 |
185 | await supplyChain.purchaseItem(upc, { from: consumerID });
186 | itemState = 7;
187 |
188 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
189 |
190 | assert.equal(resultBufferTwo[8], consumerID, 'Error: Invalid consumerID');
191 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
192 | assert.equal(eventEmitted, true, 'Error: Purchased event not emitted');
193 | });
194 |
195 |
196 | it("Testing smart contract function fetchItemBufferOne() that allows anyone to fetch item details from blockchain", async () => {
197 | const resultBufferOne = await supplyChain.fetchItemBufferOne.call(upc);
198 |
199 | assert.equal(resultBufferOne[0], sku, 'Error: Invalid item SKU');
200 | assert.equal(resultBufferOne[1], upc, 'Error: Invalid item UPC');
201 | assert.equal(resultBufferOne[2], ownerID, 'Error: Missing or Invalid ownerID');
202 | assert.equal(resultBufferOne[3], originFarmerID, 'Error: Missing or Invalid originFarmerID');
203 | assert.equal(resultBufferOne[4], originFarmName, 'Error: Missing or Invalid originFarmName');
204 | assert.equal(resultBufferOne[5], originFarmInformation, 'Error: Missing or Invalid originFarmInformation');
205 | assert.equal(resultBufferOne[6], originFarmLatitude, 'Error: Missing or Invalid originFarmLatitude');
206 | assert.equal(resultBufferOne[7], originFarmLongitude, 'Error: Missing or Invalid originFarmLongitude');
207 | });
208 |
209 | it("Testing smart contract function fetchItemBufferTwo() that allows anyone to fetch item details from blockchain", async () => {
210 | const resultBufferTwo = await supplyChain.fetchItemBufferTwo.call(upc);
211 |
212 | assert.equal(resultBufferTwo[0], sku, 'Error: Invalid item SKU');
213 | assert.equal(resultBufferTwo[1], upc, 'Error: Invalid item UPC');
214 | assert.equal(resultBufferTwo[2], productID, 'Error: Invalid item productID');
215 | assert.equal(resultBufferTwo[3], productNotes, 'Error: Invalid item productNotes');
216 | assert.equal(resultBufferTwo[4], productPrice, 'Error: Invalid item productPrice');
217 | assert.equal(resultBufferTwo[5], itemState, 'Error: Invalid item State');
218 | assert.equal(resultBufferTwo[6], distributorID, 'Error: Invalid item distributorID');
219 | assert.equal(resultBufferTwo[7], retailerID, 'Error: Invalid item retailerID');
220 | assert.equal(resultBufferTwo[8], consumerID, 'Error: Invalid item consumerID');
221 | });
222 |
--------------------------------------------------------------------------------
/contracts/coffeebase/SupplyChain.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.4.24;
2 |
3 | import "../coffeeaccesscontrol/ConsumerRole.sol";
4 | import "../coffeeaccesscontrol/DistributorRole.sol";
5 | import "../coffeeaccesscontrol/FarmerRole.sol";
6 | import "../coffeeaccesscontrol/RetailerRole.sol";
7 |
8 | contract SupplyChain is ConsumerRole, DistributorRole, FarmerRole, RetailerRole {
9 |
10 | address contractOwner;
11 | uint upc;
12 | uint sku;
13 |
14 | mapping(uint => Item) items; // (upc => Item)
15 |
16 | // Define a public mapping 'itemsHistory' that maps the UPC to an array of TxHash,
17 | // that track its journey through the supply chain -- to be sent from DApp.
18 | mapping(uint => string[]) itemsHistory; // (upc => [progress?])
19 |
20 | enum State
21 | {
22 | Harvested, // 0
23 | Processed, // 1
24 | Packed, // 2
25 | ForSale, // 3
26 | Sold, // 4
27 | Shipped, // 5
28 | Received, // 6
29 | Purchased // 7
30 | }
31 |
32 | State constant defaultState = State.Harvested;
33 |
34 | struct Item {
35 | uint sku; // Stock Keeping Unit (SKU)
36 | uint upc; // Universal Product Code (UPC), generated by the Farmer, goes on the package, can be verified by the Consumer
37 | address ownerID; // Metamask-Ethereum address of the current owner as the product moves through 8 stages
38 | address originFarmerID; // Metamask-Ethereum address of the Farmer
39 | string originFarmName; // Farmer Name
40 | string originFarmInformation; // Farmer Information
41 | string originFarmLatitude; // Farm Latitude
42 | string originFarmLongitude; // Farm Longitude
43 | uint productID; // Product ID potentially a combination of upc + sku
44 | string productNotes; // Product Notes
45 | uint productPrice; // Product Price
46 | State itemState; // Product State as represented in the enum above
47 | address distributorID; // Metamask-Ethereum address of the Distributor
48 | address retailerID; // Metamask-Ethereum address of the Retailer
49 | address consumerID; // Metamask-Ethereum address of the Consumer
50 | }
51 |
52 | event Harvested(uint upc);
53 | event Processed(uint upc);
54 | event Packed(uint upc);
55 | event ForSale(uint upc);
56 | event Sold(uint upc);
57 | event Shipped(uint upc);
58 | event Received(uint upc);
59 | event Purchased(uint upc);
60 |
61 |
62 | /* Modifiers ************************ */
63 |
64 | modifier onlyOwner() {
65 | require(msg.sender == contractOwner, "Only the owner can perform this operation");
66 | _;
67 | }
68 |
69 | modifier verifyCaller (address _address) {
70 | require(msg.sender == _address);
71 | _;
72 | }
73 |
74 | modifier paidEnough(uint _price) {
75 | require(msg.value >= _price, "Not enough was paid for the item");
76 | _;
77 | }
78 |
79 | modifier checkValue(uint _upc) {
80 | _;
81 | uint _price = items[_upc].productPrice;
82 | uint amountToReturn = msg.value - _price;
83 |
84 | address payable consumerAddressPayable = _make_payable(items[_upc].consumerID);
85 | consumerAddressPayable.transfer(amountToReturn);
86 | }
87 |
88 | modifier harvested(uint _upc) {
89 | require(items[_upc].itemState == State.Harvested, "The item is not yet harvested");
90 | _;
91 | }
92 |
93 | modifier processed(uint _upc) {
94 | require(items[_upc].itemState == State.Processed, "The item is not yet processed");
95 | _;
96 | }
97 |
98 | modifier packed(uint _upc) {
99 | require(items[_upc].itemState == State.Packed, "The item is not yet packed");
100 | _;
101 | }
102 |
103 | modifier forSale(uint _upc) {
104 | require(items[_upc].itemState == State.ForSale, "The item is not yet for sale");
105 | _;
106 | }
107 |
108 | modifier sold(uint _upc) {
109 | require(items[_upc].itemState == State.Sold, "The item is not yet sold");
110 | _;
111 | }
112 |
113 | modifier shipped(uint _upc) {
114 | require(items[_upc].itemState == State.Shipped, "The item is not yet shipped");
115 | _;
116 | }
117 |
118 | modifier received(uint _upc) {
119 | require(items[_upc].itemState == State.Received, "The item is not yet received");
120 | _;
121 | }
122 |
123 | modifier purchased(uint _upc) {
124 | require(items[_upc].itemState == State.Purchased, "The item is not yet purchased");
125 | _;
126 | }
127 |
128 |
129 | /* Constructor & utils ************************ */
130 |
131 | constructor() public payable {
132 | contractOwner = msg.sender;
133 | sku = 1;
134 | upc = 1;
135 | }
136 |
137 | function kill() public {
138 | if (msg.sender == contractOwner) {
139 | address payable ownerAddressPayable = _make_payable(contractOwner);
140 | selfdestruct(ownerAddressPayable);
141 | }
142 | }
143 |
144 | function _make_payable(address x) internal pure returns (address payable) {
145 | return address(uint160(x));
146 | }
147 |
148 | /* Functions ************************ */
149 |
150 | function harvestItem(
151 | uint _upc,
152 | address _originFarmerID,
153 | string memory _originFarmName,
154 | string memory _originFarmInformation,
155 | string memory _originFarmLatitude,
156 | string memory _originFarmLongitude,
157 | string memory _productNotes) public onlyFarmer
158 | {
159 | items[_upc] = Item({
160 | sku: sku,
161 | upc: _upc,
162 | ownerID: contractOwner,
163 | originFarmerID: _originFarmerID,
164 | originFarmName: _originFarmName,
165 | originFarmInformation: _originFarmInformation,
166 | originFarmLatitude: _originFarmLatitude,
167 | originFarmLongitude: _originFarmLongitude,
168 | productID: _upc + sku,
169 | productNotes: _productNotes,
170 | productPrice: uint(0),
171 | itemState: defaultState,
172 | distributorID: address(0),
173 | retailerID: address(0),
174 | consumerID: address(0)
175 | });
176 |
177 | sku = sku + 1;
178 |
179 | emit Harvested(_upc);
180 | }
181 |
182 | function processItem(uint _upc) public onlyFarmer harvested(_upc)
183 | {
184 | items[_upc].itemState = State.Processed;
185 | emit Processed(_upc);
186 | }
187 |
188 | function packItem(uint _upc) public onlyFarmer processed(_upc)
189 | {
190 | items[_upc].itemState = State.Packed;
191 | emit Packed(_upc);
192 | }
193 |
194 | function sellItem(uint _upc, uint _price) public onlyFarmer packed(_upc)
195 | {
196 | items[_upc].itemState = State.ForSale;
197 | items[_upc].productPrice = _price;
198 | emit ForSale(_upc);
199 | }
200 |
201 | function buyItem(uint _upc) public payable onlyDistributor forSale(_upc) paidEnough(items[_upc].productPrice)
202 | {
203 | items[_upc].ownerID = contractOwner;
204 | items[_upc].distributorID = msg.sender;
205 | items[_upc].itemState = State.Sold;
206 |
207 | address payable originFarmerAddressPayable = _make_payable(items[_upc].originFarmerID);
208 | originFarmerAddressPayable.transfer(msg.value);
209 |
210 | emit Sold(_upc);
211 | }
212 |
213 | function shipItem(uint _upc) public onlyDistributor sold(_upc)
214 | {
215 | items[_upc].itemState = State.Shipped;
216 | emit Shipped(_upc);
217 | }
218 |
219 | function receiveItem(uint _upc) public onlyRetailer shipped(_upc)
220 | {
221 | items[_upc].ownerID = contractOwner;
222 | items[_upc].retailerID = msg.sender;
223 | items[_upc].itemState = State.Received;
224 | emit Received(_upc);
225 | }
226 |
227 | function purchaseItem(uint _upc) public onlyConsumer received(_upc)
228 | {
229 | items[_upc].ownerID = contractOwner;
230 | items[_upc].consumerID = msg.sender;
231 | items[_upc].itemState = State.Purchased;
232 | emit Purchased(_upc);
233 | }
234 |
235 | function fetchItemBufferOne(uint _upc) public view returns
236 | (
237 | uint itemSKU,
238 | uint itemUPC,
239 | address ownerID,
240 | address originFarmerID,
241 | string memory originFarmName,
242 | string memory originFarmInformation,
243 | string memory originFarmLatitude,
244 | string memory originFarmLongitude
245 | )
246 | {
247 | itemSKU = items[_upc].sku;
248 | itemUPC = items[_upc].upc;
249 | ownerID = items[_upc].ownerID;
250 | originFarmerID = items[_upc].originFarmerID;
251 | originFarmName = items[_upc].originFarmName;
252 | originFarmInformation = items[_upc].originFarmInformation;
253 | originFarmLatitude = items[_upc].originFarmLatitude;
254 | originFarmLongitude = items[_upc].originFarmLongitude;
255 |
256 | return
257 | (
258 | itemSKU,
259 | itemUPC,
260 | ownerID,
261 | originFarmerID,
262 | originFarmName,
263 | originFarmInformation,
264 | originFarmLatitude,
265 | originFarmLongitude
266 | );
267 | }
268 |
269 | function fetchItemBufferTwo(uint _upc) public view returns
270 | (
271 | uint itemSKU,
272 | uint itemUPC,
273 | uint productID,
274 | string memory productNotes,
275 | uint productPrice,
276 | uint itemState,
277 | address distributorID,
278 | address retailerID,
279 | address consumerID
280 | )
281 | {
282 | itemSKU = items[_upc].sku;
283 | itemUPC = items[_upc].upc;
284 | productID = items[_upc].productID;
285 | productNotes = items[_upc].productNotes;
286 | productPrice = items[_upc].productPrice;
287 | itemState = uint(items[_upc].itemState);
288 | distributorID = items[_upc].distributorID;
289 | retailerID = items[_upc].retailerID;
290 | consumerID = items[_upc].consumerID;
291 |
292 | return
293 | (
294 | itemSKU,
295 | itemUPC,
296 | productID,
297 | productNotes,
298 | productPrice,
299 | itemState,
300 | distributorID,
301 | retailerID,
302 | consumerID
303 | );
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/website/js/app.js:
--------------------------------------------------------------------------------
1 | App = {
2 | web3Provider: null,
3 | contracts: {},
4 | emptyAddress: "0x0000000000000000000000000000000000000000",
5 | sku: 0,
6 | upc: 0,
7 | metamaskAccountID: "0x0000000000000000000000000000000000000000",
8 | ownerID: "0x0000000000000000000000000000000000000000",
9 | originFarmerID: "0x0000000000000000000000000000000000000000",
10 | originFarmName: null,
11 | originFarmInformation: null,
12 | originFarmLatitude: null,
13 | originFarmLongitude: null,
14 | productNotes: null,
15 | productPrice: 0,
16 | distributorID: "0x0000000000000000000000000000000000000000",
17 | retailerID: "0x0000000000000000000000000000000000000000",
18 | consumerID: "0x0000000000000000000000000000000000000000",
19 |
20 | init: async function () {
21 | App.readForm();
22 | return await App.initWeb3();
23 | },
24 |
25 | readForm: function () {
26 | App.sku = $("#sku").val();
27 | App.upc = $("#upc").val();
28 | App.ownerID = $("#ownerID").val();
29 | App.originFarmerID = $("#originFarmerID").val();
30 | App.originFarmName = $("#originFarmName").val();
31 | App.originFarmInformation = $("#originFarmInformation").val();
32 | App.originFarmLatitude = $("#originFarmLatitude").val();
33 | App.originFarmLongitude = $("#originFarmLongitude").val();
34 | App.productNotes = $("#productNotes").val();
35 | App.productPrice = $("#productPrice").val();
36 | App.distributorID = $("#distributorID").val();
37 | App.retailerID = $("#retailerID").val();
38 | App.consumerID = $("#consumerID").val();
39 |
40 | console.log({
41 | sku: App.sku,
42 | upc: App.upc,
43 | ownerID: App.ownerID,
44 | originFarmerID: App.originFarmerID,
45 | originFarmName: App.originFarmName,
46 | originFarmInformation: App.originFarmInformation,
47 | originFarmLatitude: App.originFarmLatitude,
48 | originFarmLongitude: App.originFarmLongitude,
49 | productNotes: App.productNotes,
50 | productPrice: App.productPrice,
51 | distributorID: App.distributorID,
52 | retailerID: App.retailerID,
53 | consumerID: App.consumerID
54 | });
55 | },
56 |
57 | initWeb3: async function () {
58 |
59 | if (window.ethereum) {
60 | App.web3Provider = window.ethereum;
61 |
62 | try {
63 | await window.ethereum.enable();
64 | console.log("Connected to web3 via window.ethereum");
65 | } catch (error) {
66 | console.error("User denied account access");
67 | }
68 | } else if (window.web3) {
69 | App.web3Provider = window.web3.currentProvider;
70 | console.log("Connected to web3 via window.web3.currentProvider");
71 | } else {
72 | console.warn("No web3 detected. Falling back to http://127.0.0.1:9545. You should remove this fallback when you deploy live",);
73 | App.web3Provider = new Web3.providers.HttpProvider("http://127.0.0.1:9545");
74 | }
75 |
76 | App.getMetaskAccountID();
77 |
78 | return App.initSupplyChain();
79 | },
80 |
81 | getMetaskAccountID: function () {
82 | web3 = new Web3(App.web3Provider);
83 |
84 | web3.eth.getAccounts(function (err, res) {
85 | if (err) {
86 | console.log('Error:', err);
87 | return;
88 | }
89 | console.log('getMetaskID:', res);
90 | App.metamaskAccountID = res[0];
91 | });
92 | },
93 |
94 | initSupplyChain: function () {
95 | var jsonSupplyChain = './SupplyChain.json';
96 |
97 | $.getJSON(jsonSupplyChain, function (data) {
98 | console.log('data', data);
99 | var SupplyChainArtifact = data;
100 | App.contracts.SupplyChain = TruffleContract(SupplyChainArtifact);
101 | App.contracts.SupplyChain.setProvider(App.web3Provider);
102 |
103 | App.fetchItemBufferOne();
104 | App.fetchItemBufferTwo();
105 | App.fetchEvents();
106 | });
107 |
108 | return App.bindEvents();
109 | },
110 |
111 | bindEvents: function () {
112 | $(document).on('click', App.handleButtonClick);
113 | },
114 |
115 | handleButtonClick: async function (event) {
116 | event.preventDefault();
117 |
118 | App.getMetaskAccountID();
119 |
120 | var processId = parseInt($(event.target).data('id'));
121 | console.log('processId', processId);
122 |
123 | switch (processId) {
124 | case 1:
125 | return await App.harvestItem(event);
126 | break;
127 | case 2:
128 | return await App.processItem(event);
129 | break;
130 | case 3:
131 | return await App.packItem(event);
132 | break;
133 | case 4:
134 | return await App.sellItem(event);
135 | break;
136 | case 5:
137 | return await App.buyItem(event);
138 | break;
139 | case 6:
140 | return await App.shipItem(event);
141 | break;
142 | case 7:
143 | return await App.receiveItem(event);
144 | break;
145 | case 8:
146 | return await App.purchaseItem(event);
147 | break;
148 | case 9:
149 | return await App.fetchItemBufferOne(event);
150 | break;
151 | case 10:
152 | return await App.fetchItemBufferTwo(event);
153 | break;
154 | }
155 | },
156 |
157 | harvestItem: function (event) {
158 | event.preventDefault();
159 | var processId = parseInt($(event.target).data('id'));
160 |
161 | App.contracts.SupplyChain.deployed().then(function (instance) {
162 | return instance.harvestItem(
163 | App.upc,
164 | App.metamaskAccountID,
165 | App.originFarmName,
166 | App.originFarmInformation,
167 | App.originFarmLatitude,
168 | App.originFarmLongitude,
169 | App.productNotes
170 | );
171 | }).then(function (result) {
172 | $("#ftc-item").text(result);
173 | console.log('harvestItem', result);
174 | }).catch(function (err) {
175 | console.log(err.message);
176 | });
177 | },
178 |
179 | processItem: function (event) {
180 | event.preventDefault();
181 | var processId = parseInt($(event.target).data('id'));
182 |
183 | App.contracts.SupplyChain.deployed().then(function (instance) {
184 | return instance.processItem(App.upc, {from: App.metamaskAccountID});
185 | }).then(function (result) {
186 | $("#ftc-item").text(result);
187 | console.log('processItem', result);
188 | }).catch(function (err) {
189 | console.log(err.message);
190 | });
191 | },
192 |
193 | packItem: function (event) {
194 | event.preventDefault();
195 | var processId = parseInt($(event.target).data('id'));
196 |
197 | App.contracts.SupplyChain.deployed().then(function (instance) {
198 | return instance.packItem(App.upc, {from: App.metamaskAccountID});
199 | }).then(function (result) {
200 | $("#ftc-item").text(result);
201 | console.log('packItem', result);
202 | }).catch(function (err) {
203 | console.log(err.message);
204 | });
205 | },
206 |
207 | sellItem: function (event) {
208 | event.preventDefault();
209 | var processId = parseInt($(event.target).data('id'));
210 |
211 | App.contracts.SupplyChain.deployed().then(function (instance) {
212 | const productPrice = web3.toWei(1, "ether");
213 | console.log('productPrice', productPrice);
214 | return instance.sellItem(App.upc, App.productPrice, {from: App.metamaskAccountID});
215 | }).then(function (result) {
216 | $("#ftc-item").text(result);
217 | console.log('sellItem', result);
218 | }).catch(function (err) {
219 | console.log(err.message);
220 | });
221 | },
222 |
223 | buyItem: function (event) {
224 | event.preventDefault();
225 | var processId = parseInt($(event.target).data('id'));
226 |
227 | App.contracts.SupplyChain.deployed().then(function (instance) {
228 | const walletValue = web3.toWei(3, "ether");
229 | return instance.buyItem(App.upc, {from: App.metamaskAccountID, value: walletValue});
230 | }).then(function (result) {
231 | $("#ftc-item").text(result);
232 | console.log('buyItem', result);
233 | }).catch(function (err) {
234 | console.log(err.message);
235 | });
236 | },
237 |
238 | shipItem: function (event) {
239 | event.preventDefault();
240 | var processId = parseInt($(event.target).data('id'));
241 |
242 | App.contracts.SupplyChain.deployed().then(function (instance) {
243 | return instance.shipItem(App.upc, {from: App.metamaskAccountID});
244 | }).then(function (result) {
245 | $("#ftc-item").text(result);
246 | console.log('shipItem', result);
247 | }).catch(function (err) {
248 | console.log(err.message);
249 | });
250 | },
251 |
252 | receiveItem: function (event) {
253 | event.preventDefault();
254 | var processId = parseInt($(event.target).data('id'));
255 |
256 | App.contracts.SupplyChain.deployed().then(function (instance) {
257 | return instance.receiveItem(App.upc, {from: App.metamaskAccountID});
258 | }).then(function (result) {
259 | $("#ftc-item").text(result);
260 | console.log('receiveItem', result);
261 | }).catch(function (err) {
262 | console.log(err.message);
263 | });
264 | },
265 |
266 | purchaseItem: function (event) {
267 | event.preventDefault();
268 | var processId = parseInt($(event.target).data('id'));
269 |
270 | App.contracts.SupplyChain.deployed().then(function (instance) {
271 | return instance.purchaseItem(App.upc, {from: App.metamaskAccountID});
272 | }).then(function (result) {
273 | $("#ftc-item").text(result);
274 | console.log('purchaseItem', result);
275 | }).catch(function (err) {
276 | console.log(err.message);
277 | });
278 | },
279 |
280 | fetchItemBufferOne: function () {
281 | event.preventDefault();
282 | var processId = parseInt($(event.target).data('id'));
283 |
284 | App.upc = $('#upc').val();
285 | console.log('upc', App.upc);
286 |
287 | App.contracts.SupplyChain.deployed().then(function (instance) {
288 | return instance.fetchItemBufferOne(App.upc);
289 | }).then(function (result) {
290 | $("#ftc-item").text(result);
291 | console.log('fetchItemBufferOne', result);
292 | }).catch(function (err) {
293 | console.log(err.message);
294 | });
295 | },
296 |
297 | fetchItemBufferTwo: function () {
298 | event.preventDefault();
299 | var processId = parseInt($(event.target).data('id'));
300 |
301 | App.contracts.SupplyChain.deployed().then(function (instance) {
302 | return instance.fetchItemBufferTwo.call(App.upc);
303 | }).then(function (result) {
304 | $("#ftc-item").text(result);
305 | console.log('fetchItemBufferTwo', result);
306 | }).catch(function (err) {
307 | console.log(err.message);
308 | });
309 | },
310 |
311 | fetchEvents: function () {
312 | if (typeof App.contracts.SupplyChain.currentProvider.sendAsync !== "function") {
313 | App.contracts.SupplyChain.currentProvider.sendAsync = function () {
314 | return App.contracts.SupplyChain.currentProvider.send.apply(
315 | App.contracts.SupplyChain.currentProvider,
316 | arguments
317 | );
318 | };
319 | }
320 |
321 | App.contracts.SupplyChain.deployed().then(function (instance) {
322 | var events = instance.allEvents(function (err, log) {
323 | if (!err)
324 | $("#ftc-events").append('' + log.event + ' - ' + log.transactionHash + '');
325 | });
326 | }).catch(function (err) {
327 | console.log(err.message);
328 | });
329 | }
330 | };
331 |
332 | $(function () {
333 | $(window).load(function () {
334 | App.init();
335 | });
336 | });
337 |
--------------------------------------------------------------------------------