Deploy & Interact with a KYC DApp
Students will compile, deploy, and interact with a KYC smart contract, then build a tiny front‑end using Web3.
Instructional Videos
- 🎥 Video 1: Smart Contract (KYC.sol)
- 🎥 Video 2: Truffle Console (optional)
- 🎥 Video 3: Web3 Front‑End (part 1)
- 🎥 Video 4: Web3 Front‑End (part 2)
(Instructor: paste links here.)
1) Set Up Your Environment
- Install Node.js (LTS) → verify:
node -v npm -v
- Install Truffle (global) → verify:
npm install -g truffle truffle version
- Install Ganache (GUI or CLI) and start a fresh workspace (
RPC http://127.0.0.1:7545
).
2) Initialize a Truffle Project
mkdir my-project
cd my-project
truffle init
Set truffle-config.js
for Ganache and Solidity 0.8.x:
module.exports = {
networks: {
development: { host: "127.0.0.1", port: 7545, network_id: "*" },
},
compilers: { solc: { version: "0.8.20" } }
};
3) Write Your KYC Smart Contract
Create contracts/KYC.sol
and paste:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract KYC {
struct Bank { string name; address bankAddress; }
struct Customer { string name; address customerAddress; bool isVerified; }
mapping(address => Bank) public banks;
mapping(address => Customer) public customers;
address[] public bankList;
address[] public customerList;
event BankAdded(string name, address bankAddress);
event CustomerAdded(string name, address customerAddress);
event CustomerVerified(address customerAddress);
function addNewBank(string memory name, address bankAddress) public {
require(bankAddress != address(0), "Invalid address");
require(bytes(name).length > 0, "Bank name required");
banks[bankAddress] = Bank(name, bankAddress);
bankList.push(bankAddress);
emit BankAdded(name, bankAddress);
}
function addNewCustomer(string memory name, address customerAddress) public {
require(customerAddress != address(0), "Invalid address");
require(bytes(name).length > 0, "Customer name required");
customers[customerAddress] = Customer(name, customerAddress, false);
customerList.push(customerAddress);
emit CustomerAdded(name, customerAddress);
}
function verifyCustomer(address customerAddress) public {
require(customerAddress != address(0), "Invalid address");
require(customers[customerAddress].customerAddress != address(0), "Customer missing");
customers[customerAddress].isVerified = true;
emit CustomerVerified(customerAddress);
}
function getBanks() public view returns (Bank[] memory) {
Bank[] memory allBanks = new Bank[](bankList.length);
for (uint i = 0; i < bankList.length; i++) {
allBanks[i] = banks[bankList[i]];
}
return allBanks;
}
function getCustomers() public view returns (Customer[] memory) {
Customer[] memory allCustomers = new Customer[](customerList.length);
for (uint i = 0; i < customerList.length; i++) {
allCustomers[i] = customers[customerList[i]];
}
return allCustomers;
}
}
Compile:
truffle compile
4) Migration Script & Deploy
Create migrations/2_deploy_contracts.js
:
const KYC = artifacts.require("KYC");
module.exports = function(deployer){ deployer.deploy(KYC); };
Start Ganache, then deploy:
truffle migrate --network development
build/contracts/KYC.json
.5) Interact via Truffle Console (optional but helpful)
truffle console --network development
const KYC = artifacts.require("KYC");
const kyc = await KYC.deployed();
const accounts = await web3.eth.getAccounts();
// Add banks
await kyc.addNewBank("Bank1", accounts[1]);
await kyc.addNewBank("Bank2", accounts[2]);
// Add customers
await kyc.addNewCustomer("Customer1", accounts[3]);
await kyc.addNewCustomer("Customer2", accounts[4]);
// Verify
await kyc.verifyCustomer(accounts[3]);
// Read back
const banks = await kyc.getBanks();
const customers = await kyc.getCustomers();
console.log(banks);
console.log(customers);
6) Minimal Front‑End (Vanilla Web3, no React)
We’ll serve a simple HTML page with lite-server
and auto‑load ABI + address from Truffle’s build JSON.
6.1 Create front‑end folder & install tools
mkdir frontend
cd frontend
npm init -y
npm install web3 lite-server --save-dev
6.2 Add package.json
script
{
"name": "kyc-dapp",
"version": "1.0.0",
"private": true,
"scripts": { "start": "lite-server" },
"devDependencies": { "lite-server": "^2.6.1" },
"dependencies": { "web3": "^1.10.4" }
}
6.3 Add bs-config.json
(so the browser can fetch ../build/
)
{ "server": { "baseDir": ["./", "../"] } }
6.4 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>KYC DApp</title>
<style> body{font-family:Arial,sans-serif;background:#f4f4f4;padding:20px} .card{max-width:720px;margin:auto;background:#fff;padding:20px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1)} input,button{padding:10px;margin:6px 0;border-radius:6px;border:1px solid #ccc} button{background:#2563eb;color:#fff;border:none;cursor:pointer} button:hover{opacity:.9} table{width:100%;border-collapse:collapse;margin-top:10px} td,th{border-bottom:1px solid #e5e7eb;padding:8px;text-align:left} .muted{color:#6b7280} </style>
</head>
<body>
<div class="card">
<h1>KYC DApp</h1>
<button id="connect">Connect MetaMask</button>
<div id="who" class="muted"></div>
<h2>Add New Bank</h2>
<input id="bankName" placeholder="Bank Name"/>
<input id="bankAddr" placeholder="Bank Address 0x..."/>
<button id="addBank">Add Bank</button>
<h2>Add New Customer</h2>
<input id="custName" placeholder="Customer Name"/>
<input id="custAddr" placeholder="Customer Address 0x..."/>
<button id="addCust">Add Customer</button>
<h2>Verify Customer</h2>
<input id="verifyAddr" placeholder="Customer Address 0x..."/>
<button id="verifyBtn">Verify</button>
<p id="status" class="muted"></p>
<h3>Banks</h3>
<table id="banksTbl"><thead><tr><th>Name</th><th>Address</th></tr></thead><tbody></tbody></table>
<h3>Customers</h3>
<table id="custTbl"><thead><tr><th>Name</th><th>Address</th><th>Verified</th></tr></thead><tbody></tbody></table>
</div>
<script src="https://cdn.jsdelivr.net/npm/web3@1.10.4/dist/web3.min.js"></script>
<script src="app.js"></script>
</body>
</html>
6.5 app.js
let web3, accounts, kyc;
const ABI_URL = '../build/contracts/KYC.json';
const el = id => document.getElementById(id);
const setStatus = m => el('status').textContent = m;
async function connect(){
if(!window.ethereum){ alert('Please install MetaMask'); return; }
web3 = new Web3(window.ethereum);
accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
el('who').textContent = `Connected as ${accounts[0]}`;
await loadContract();
}
async function loadContract(){
const res = await fetch(ABI_URL); const json = await res.json();
const abi = json.abi;
let addr = '';
const nets = json.networks || {}; const ids = Object.keys(nets);
if(ids.length){ addr = nets[ids[ids.length-1]].address; }
if(!addr){ setStatus('❗ Set contract address in app.js or redeploy.'); return; }
kyc = new web3.eth.Contract(abi, addr);
setStatus('Contract loaded: ' + addr);
await refreshTables();
}
async function refreshTables(){
const banks = await kyc.methods.getBanks().call();
const custs = await kyc.methods.getCustomers().call();
el('banksTbl').querySelector('tbody').innerHTML = banks.map(b=>`${b.name} ${b.bankAddress} `).join('');
el('custTbl').querySelector('tbody').innerHTML = custs.map(c=>`${c.name} ${c.customerAddress} ${c.isVerified} `).join('');
}
async function addBank(){
const name = el('bankName').value.trim(); const addr = el('bankAddr').value.trim();
if(!name || !web3.utils.isAddress(addr)) return setStatus('Enter bank name and valid address');
try{
const gas = await kyc.methods.addNewBank(name, addr).estimateGas({ from: accounts[0] });
await kyc.methods.addNewBank(name, addr).send({ from: accounts[0], gas });
setStatus('✅ Bank added'); await refreshTables();
}catch(e){ setStatus('Error: ' + e.message); }
}
async function addCustomer(){
const name = el('custName').value.trim(); const addr = el('custAddr').value.trim();
if(!name || !web3.utils.isAddress(addr)) return setStatus('Enter customer name and valid address');
try{
const gas = await kyc.methods.addNewCustomer(name, addr).estimateGas({ from: accounts[0] });
await kyc.methods.addNewCustomer(name, addr).send({ from: accounts[0], gas });
setStatus('✅ Customer added'); await refreshTables();
}catch(e){ setStatus('Error: ' + e.message); }
}
async function verify(){
const addr = el('verifyAddr').value.trim();
if(!web3.utils.isAddress(addr)) return setStatus('Enter a valid address');
try{
const gas = await kyc.methods.verifyCustomer(addr).estimateGas({ from: accounts[0] });
await kyc.methods.verifyCustomer(addr).send({ from: accounts[0], gas });
setStatus('✅ Customer verified'); await refreshTables();
}catch(e){ setStatus('Error: ' + e.message); }
}
el('connect').addEventListener('click', connect);
el('addBank').addEventListener('click', addBank);
el('addCust').addEventListener('click', addCustomer);
el('verifyBtn').addEventListener('click', verify);
Prefer React instead?
Use Create React App and port the same three calls (addNewBank
, addNewCustomer
, verifyCustomer
) into event handlers. If you see a Babel warning, add @babel/plugin-proposal-private-property-in-object
as a dev dependency.
7) Run & Test
- Start Ganache (GUI quickstart, RPC
7545
). - In
my-project
deploy the contract:truffle migrate --network development
. - In
my-project/frontend
:npm start
→ openhttp://localhost:3000
. - MetaMask → switch to the Ganache network (host
127.0.0.1
, port7545
, chain ID 1337/5777). - Connect, add a bank, add a customer, verify customer → watch updates in the tables.
Troubleshooting (Quick Fixes)
- Wrong address/ABI: Ensure the front‑end fetches
../build/contracts/KYC.json
and that you redeployed after edits. - MetaMask on wrong network: Switch to your Ganache RPC; import a Ganache account if you want to act as sender.
- Port 3000 busy: stop other dev servers or configure
lite-server
to another port. - Ganache reset: Restart the workspace if transactions look stuck; in MetaMask you can also reset nonce (dev only).
- Gas errors: Use
estimateGas
as shown; ensure sender has ETH (Ganache prefunds accounts).
Advanced Version (Admin + Permissions)
The appendix in your spec mentions an admin (kycadmin) and bank permissions (canAddCustomers/canPerformKYC). That’s a stricter model. For class, start with the beginner contract above. As extra credit, evolve it by adding an owner
, modifiers, and permission bits on the Bank struct, then gate the functions accordingly.
Submission Checklist
- Screenshot: Ganache workspace running.
- Screenshot:
truffle migrate
success (showing KYC address). - Screenshot: Truffle console after adding banks/customers + verifying one.
- Screenshot: Web UI (tables populated after your actions).
- Zip:
my-project/
excludingnode_modules
. Includefrontend/
withindex.html
,app.js
,package.json
,bs-config.json
.
TP1_KYC_FirstnameLastname