├── .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 |
18 |

Product Overview

19 |
20 | SKU 21 |
22 |
23 | UPC 24 |
25 |
26 | Current Owner ID 27 |
28 |
29 |
30 | 31 | 32 |
33 |
34 |
35 |

Farm Details

36 |
37 | Farmer ID 38 |
39 |
41 | Farm Name 42 |
43 |
44 | Farm Information 45 |
46 |
47 | Farm Latitude 48 |
49 |
50 | Farm Longitude 51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 |
59 |
60 |

Product Details

61 |
62 | Product Notes 63 |
64 |
65 | Product Price 66 |
67 | ETH
68 | Distributor ID 69 |
70 | 72 |
73 | Retailer ID 74 |
75 |
77 | Distributor ID 78 |
79 |
81 |
82 | 83 | 84 | 85 | 86 |
87 |
88 |
89 |
90 | 91 |

Transaction History

92 |
93 | 96 |
97 |
98 |
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 | --------------------------------------------------------------------------------