Term Project 2 – Simple Fraternity DApp
Truffle • Ganache • MetaMask • Web3 Front‑End

Build a Simple Fraternity Registration DApp

You’ll deploy a FraternityRegistration contract and build a minimal Web UI to register members and check membership via MetaMask → Ganache.

Tip: Use only local networks for this project (Ganache). Do not deploy on mainnet.
Reference

This guide matches your provided spec and fills in missing details (ABI, network config, and front‑end wiring).

Fraternity DApp – Project Structure

fraternity-dapp/
├─ contracts/
│  └─ FraternityRegistration.sol
├─ migrations/
│  └─ 2_deploy_contracts.js
├─ build/
│  └─ contracts/
│     └─ FraternityRegistration.json   # generated after compile
├─ frontend/
│  ├─ index.html
│  ├─ app.js
│  ├─ package.json
│  └─ bs-config.json                    # (added) serve parent build/ to the browser
├─ truffle-config.js
└─ README.md

1) Initialize Truffle Project

mkdir fraternity-dapp
cd fraternity-dapp
truffle init

Configure truffle-config.js for Ganache GUI (port 7545):

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*",
    },
  },
  compilers: {
    solc: { version: "0.8.20" }
  }
};

2) Smart Contract

Create contracts/FraternityRegistration.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract FraternityRegistration {
    address public owner;
    uint256 public totalMembers;

    struct Member { string name; bool isRegistered; }
    mapping(address => Member) public members;

    event MemberRegistered(address indexed memberAddress, string name);
    event MemberRemoved(address indexed memberAddress);

    modifier onlyOwner(){ require(msg.sender == owner, "Only owner"); _; }

    constructor(){ owner = msg.sender; }

    function registerMember(address _memberAddress, string memory _name) public onlyOwner {
        require(!members[_memberAddress].isRegistered, "Already registered");
        members[_memberAddress] = Member({ name:_name, isRegistered:true });
        totalMembers++;
        emit MemberRegistered(_memberAddress, _name);
    }

    function isMember(address _memberAddress) public view returns (bool) {
        return members[_memberAddress].isRegistered;
    }

    function getMemberDetails(address _memberAddress) public view returns (string memory) {
        require(members[_memberAddress].isRegistered, "Not registered");
        return members[_memberAddress].name;
    }

    function removeMember(address _memberAddress) public onlyOwner {
        require(members[_memberAddress].isRegistered, "Not registered");
        delete members[_memberAddress];
        totalMembers--;
        emit MemberRemoved(_memberAddress);
    }

    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != address(0), "Invalid owner");
        owner = _newOwner;
    }
}

Compile:

truffle compile

3) Migration & Deploy

Create migrations/2_deploy_contracts.js:

const FraternityRegistration = artifacts.require("FraternityRegistration");

module.exports = function (deployer) {
  deployer.deploy(FraternityRegistration);
};

Start Ganache (GUI quickstart → RPC http://127.0.0.1:7545) and deploy:

truffle migrate --network development
# Confirm address in the output
# (Optional) Get it again in the console:
truffle console --network development
(Finality) const d = await FraternityRegistration.deployed(); d.address
Save the deployed contract address for the front‑end.

4) Front‑End (Web3 + lite‑server)

4.1 Create front‑end folder & packages

mkdir frontend
cd frontend
npm init -y
npm install web3 lite-server --save-dev

4.2 Add package.json scripts

{
  "name": "fraternity-dapp",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "start": "lite-server"
  },
  "devDependencies": {
    "lite-server": "^2.6.1"
  },
  "dependencies": {
    "web3": "^1.10.4"
  }
}

We serve Web3 via CDN below; the npm install is just to keep dependencies tracked.

4.3 bs-config.json (lets browser read ../build/)

{
  "server": { "baseDir": ["./", "../"] }
}

4.4 index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>JU Baseball & Cross Team – Decentralized Registration</title>
</head>
<body style="font-family: Arial, sans-serif; background:#f4f4f4; color:#333">
  <h1 style="text-align:center; color:navy">Jacksonville University Baseball and Cross Team Decentralized Registration</h1>
  <div style="max-width:720px; margin:auto; padding:20px; background:#fff; border-radius:10px; box-shadow:0 0 10px rgba(0,0,0,.1)">
    <button id="connect" style="width:100%; padding:10px; background:teal; color:#fff; border:none; border-radius:6px; cursor:pointer">Connect MetaMask</button>

    <h2 style="color:darkblue; margin-top:16px">Register a New Member</h2>
    <label>Member Address</label>
    <input type="text" id="memberAddress" placeholder="0x..." style="width:100%; padding:10px; margin-bottom:10px; border:1px solid #ccc; border-radius:5px" />
    <label>Member Name</label>
    <input type="text" id="memberName" placeholder="Full name" style="width:100%; padding:10px; margin-bottom:10px; border:1px solid #ccc; border-radius:5px" />
    <button id="registerBtn" style="width:100%; padding:10px; background:darkblue; color:#fff; border:none; border-radius:5px; cursor:pointer">Register Member</button>

    <h2 style="color:darkblue; margin-top:20px">Check Membership</h2>
    <button id="checkBtn" style="width:100%; padding:10px; background:navy; color:#fff; border:none; border-radius:5px; cursor:pointer">Check Membership</button>

    <div id="result" style="margin-top:20px; color:darkred"></div>
    <div id="owner" style="margin-top:8px; color:green"></div>
    <div id="stats" style="margin-top:8px; color:#444"></div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/web3@1.10.4/dist/web3.min.js"></script>
  <script src="app.js"></script>
</body>
</html>

4.5 app.js

// Auto-load ABI and address from Truffle build, fallback to manual address
let web3, accounts, contract;
let contractAddress = ""; // OPTIONAL: paste manual address if fetch fails

const ABI_URL = "../build/contracts/FraternityRegistration.json";

async function connect(){
  if(!window.ethereum){ alert('Please install MetaMask'); return; }
  web3 = new Web3(window.ethereum);
  accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
  document.getElementById('result').textContent = `Connected as ${accounts[0]}`;
  await loadContract();
}

async function loadContract(){
  try{
    const res = await fetch(ABI_URL);
    const json = await res.json();
    const abi = json.abi;
    // Try to derive address from networks key
    const nets = json.networks || {};
    const netIds = Object.keys(nets);
    if(!contractAddress && netIds.length){ contractAddress = nets[netIds[netIds.length-1]].address; }
    if(!contractAddress) throw new Error('Set contractAddress in app.js');
    contract = new web3.eth.Contract(abi, contractAddress);
    document.getElementById('owner').textContent = 'Contract: ' + contractAddress;
    // Show owner & totalMembers
    const [owner, total] = await Promise.all([
      contract.methods.owner().call(),
      contract.methods.totalMembers().call()
    ]);
    document.getElementById('stats').textContent = `Owner: ${owner} • Total Members: ${total}`;
  }catch(e){
    console.error(e); document.getElementById('result').textContent = 'Load contract failed: ' + e.message;
  }
}

async function registerMember(){
  const addr = document.getElementById('memberAddress').value.trim();
  const name = document.getElementById('memberName').value.trim();
  if(!web3.utils.isAddress(addr)) return (document.getElementById('result').textContent='Invalid address');
  const btn = document.getElementById('registerBtn'); btn.disabled = true;
  try{
    const gas = await contract.methods.registerMember(addr, name).estimateGas({ from: accounts[0] });
    await contract.methods.registerMember(addr, name).send({ from: accounts[0], gas });
    document.getElementById('result').textContent = 'Member registered ✅';
    const total = await contract.methods.totalMembers().call();
    document.getElementById('stats').textContent = `Owner: ${(await contract.methods.owner().call())} • Total Members: ${total}`;
  }catch(e){ document.getElementById('result').textContent = 'Error: ' + e.message; }
  finally{ btn.disabled=false; }
}

async function checkMembership(){
  const addr = document.getElementById('memberAddress').value.trim();
  if(!web3.utils.isAddress(addr)) return (document.getElementById('result').textContent='Invalid address');
  try{
    const isM = await contract.methods.isMember(addr).call();
    document.getElementById('result').textContent = isM ? '✅ This address is a member.' : '❌ Not a member.';
    if(isM){
      try{ const name = await contract.methods.getMemberDetails(addr).call();
        document.getElementById('result').textContent += ` Name: ${name}`;
      }catch(_){ /* ignore */ }
    }
  }catch(e){ document.getElementById('result').textContent = 'Error: ' + e.message; }
}

document.getElementById('connect').addEventListener('click', connect);
window.registerMember = registerMember;
window.checkMembership = checkMembership;
Prefer a hard‑coded ABI? (inline example)

Instead of fetching from ../build/, you may paste the ABI + set contractAddress manually. Ensure it includes isMember and getMemberDetails (the earlier snippet missed them).

5) Run & Test

  1. Start Ganache (GUI quickstart).
  2. In a terminal at fraternity-dapp/frontend:
    npm start
  3. Open http://localhost:3000 → click Connect MetaMask and switch to your Ganache network.
  4. Optionally import a Ganache account private key into MetaMask so you can act as the owner (the deployer).
  5. Register a member (owner only), then check membership.

Troubleshooting

Security note: This demo has no access controls beyond onlyOwner. Don’t use on public networks. Avoid storing sensitive data on‑chain.