DApp 개발: 카운터에서 감사 트래커로
발환경ganache : 로컬 이더리움 환경
Remix : 솔리디티 작성 및 배포
VisualStudio : 프론트 엔드 웹
메타마스크 : 지갑
카운터 컨트랙트 소스코드
1. 솔리디티 스마트 컨트랙트 (AuditTracker.sol)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AuditTracker {
// 상태 변수 (접근 제어자를 private으로 변경하여 안전성 확보)
uint256 private _auditCount;
// 블록체인에 로그를 남기기 위한 이벤트 선언
event AuditRecorded(address indexed auditor, uint256 newTotal);
// 점검 횟수 증가 로직 (기존 increment)
function recordAudit() public {
_auditCount += 1;
emit AuditRecorded(msg.sender, _auditCount);
}
// 현재 점검 횟수 조회 (기존 getCount)
function getAuditCount() public view returns (uint256) {
return _auditCount;
}
}

2. DApp 프론트엔드 웹 코드 (index.html)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>보안 점검 트래커 DApp</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.app-container {
background: #ffffff;
padding: 40px;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
text-align: center;
width: 100%;
max-width: 400px;
}
h2 { color: #2c3e50; margin-bottom: 20px; }
.display-panel {
background: #eef2f5;
padding: 20px;
border-radius: 8px;
font-size: 1.2rem;
margin-bottom: 25px;
color: #34495e;
}
.display-panel span {
font-weight: bold;
color: #e74c3c;
font-size: 1.5rem;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 12px;
}
button {
padding: 12px;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: bold;
color: white;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-connect { background-color: #f39c12; }
.btn-connect:hover { background-color: #d68910; }
.btn-read { background-color: #3498db; }
.btn-read:hover { background-color: #2980b9; }
.btn-write { background-color: #2ecc71; }
.btn-write:hover { background-color: #27ae60; }
</style>
</head>
<body>
<div class="app-container">
<h2>🛡️ 보안 점검 트래커</h2>
<div class="display-panel">
누적 점검 완료 횟수: <br><br>
<span id="auditDisplay">-</span> 회
</div>
<div class="btn-group">
<button class="btn-connect" onclick="initWallet()">🦊 메타마스크 지갑 연결</button>
<button class="btn-read" onclick="fetchAuditCount()">🔄 최신 데이터 조회</button>
<button class="btn-write" onclick="submitAudit()">📝 점검 기록 추가 (+1)</button>
</div>
</div>
<!-- ethers v6 라이브러리 -->
<script src="https://cdn.jsdelivr.net/npm/ethers@6.10.0/dist/ethers.umd.min.js"></script>
<script>
const { ethers } = window;
let web3Provider;
let userSigner;
let trackerContract;
// 🔥 Remix에서 배포 후 발급받은 Contract 주소로 반드시 변경하세요.
const CONTRACT_ADDRESS = "0xYOUR_NEW_CONTRACT_ADDRESS_HERE";
// 변경된 스마트 컨트랙트의 ABI
const CONTRACT_ABI = [
"function recordAudit()",
"function getAuditCount() view returns (uint256)"
];
// 1. 지갑 연결 및 초기화
async function initWallet() {
try {
if (!window.ethereum) {
alert("메타마스크(MetaMask) 확장 프로그램이 설치되어 있어야 합니다.");
return;
}
web3Provider = new ethers.BrowserProvider(window.ethereum);
await web3Provider.send("eth_requestAccounts", []);
userSigner = await web3Provider.getSigner();
// 컨트랙트 배포 여부 사전 검증
const codeAtAddress = await web3Provider.getCode(CONTRACT_ADDRESS);
if (codeAtAddress === "0x") {
alert("⚠️ 설정된 주소에 컨트랙트가 존재하지 않습니다. Ganache 네트워크와 컨트랙트 주소를 다시 확인해주세요.");
return;
}
trackerContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, userSigner);
alert("✅ 지갑이 성공적으로 연결되었습니다.");
// 연결 후 즉시 데이터 조회
fetchAuditCount();
} catch (error) {
console.error("지갑 연결 에러:", error);
alert("지갑 연결에 실패했습니다.");
}
}
// 2. 블록체인에서 데이터 읽어오기
async function fetchAuditCount() {
try {
if (!trackerContract) {
alert("먼저 지갑을 연결해주세요.");
return;
}
const currentCount = await trackerContract.getAuditCount();
document.getElementById("auditDisplay").innerText = currentCount.toString();
} catch (error) {
console.error("데이터 조회 에러:", error);
alert("데이터를 불러오는 중 오류가 발생했습니다.");
}
}
// 3. 블록체인에 데이터 쓰기 (트랜잭션 발생)
async function submitAudit() {
try {
if (!trackerContract) {
alert("먼저 지갑을 연결해주세요.");
return;
}
// 트랜잭션 전송
const transaction = await trackerContract.recordAudit();
console.log("트랜잭션 대기 중... Hash:", transaction.hash);
// 블록에 마이닝될 때까지 대기
await transaction.wait();
alert("🎉 점검 기록이 블록체인에 성공적으로 저장되었습니다!");
// 화면 즉시 갱신
fetchAuditCount();
} catch (error) {
console.error("트랜잭션 에러:", error);
alert("트랜잭션 처리에 실패했습니다.");
}
}
// 메타마스크 계정이나 네트워크 변경 시 페이지 자동 새로고침 방어코드
if (window.ethereum) {
window.ethereum.on("accountsChanged", () => window.location.reload());
window.ethereum.on("chainChanged", () => window.location.reload());
}
</script>
</body>
</html>