-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdashboard.html
More file actions
319 lines (296 loc) · 18.9 KB
/
Copy pathdashboard.html
File metadata and controls
319 lines (296 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blockchain Lab Master Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.13.4/ethers.umd.min.js"></script>
<style>
@keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.animate-in { animation: fade-in 0.4s ease-out forwards; }
.line-v { width: 2px; height: 24px; background: #4b5563; margin: 0 auto; }
.line-h { width: 50%; height: 2px; background: #4b5563; margin: 0 auto; position: relative; }
.line-h::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen p-4 md:p-8 font-sans">
<div class="max-w-6xl mx-auto space-y-6">
<header class="bg-gray-800 rounded-2xl border border-gray-700 p-6 shadow-xl flex flex-col md:flex-row justify-between items-center gap-4">
<div>
<h1 id="mainTitle" class="text-2xl font-bold text-white tracking-tight">Blockchain Lab #3</h1>
<p id="subTitle" class="text-gray-400 text-xs">Sepolia L1 & L2 Ecosystem</p>
</div>
<div class="flex gap-2 p-1 bg-gray-900 rounded-xl border border-gray-700">
<button onclick="switchNetwork('sepolia')" class="px-3 py-1.5 text-xs font-bold rounded-lg transition-all hover:bg-blue-600/20 text-blue-400 border border-blue-500/30">Sepolia</button>
<button onclick="switchNetwork('base')" class="px-3 py-1.5 text-xs font-bold rounded-lg transition-all hover:bg-green-600/20 text-green-400 border border-green-500/30">Base</button>
<button onclick="switchNetwork('giwa')" class="px-3 py-1.5 text-xs font-bold rounded-lg transition-all hover:bg-purple-600/20 text-purple-400 border border-purple-500/30">GIWA</button>
</div>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-800 rounded-2xl border border-gray-700 p-6 shadow-xl space-y-4">
<div class="flex justify-between items-center">
<h2 class="font-bold text-blue-400">Wallet Info</h2>
<button id="connectBtn" class="bg-blue-600 hover:bg-blue-500 px-4 py-1 rounded-lg text-xs font-bold transition-all">Connect</button>
</div>
<div id="walletDisplay" class="hidden space-y-3 p-4 bg-gray-900 rounded-xl border border-gray-700 font-mono text-[11px]">
<p class="text-gray-500 uppercase tracking-tighter">Address: <span id="userAddress" class="text-white break-all"></span></p>
<p class="text-gray-500 uppercase tracking-tighter">Balance: <span id="userBalance" class="text-yellow-400 font-bold text-sm"></span></p>
</div>
<div class="pt-4 border-t border-gray-700 space-y-3">
<input id="toAddr" type="text" placeholder="Recipient (Account 2)" class="w-full bg-gray-900 border border-gray-700 rounded-xl px-4 py-3 text-xs outline-none focus:border-green-500 font-mono">
<button id="sendBtn" class="w-full bg-green-600 hover:bg-green-500 text-white font-bold py-3 rounded-xl transition-all active:scale-95">ETH 송금 실행</button>
</div>
</div>
<div class="bg-gray-800 rounded-2xl border border-gray-700 p-6 shadow-xl space-y-4">
<h2 class="text-xl font-bold text-indigo-400">TX & Signature Analyzer</h2>
<div class="flex gap-2">
<input id="trackHash" type="text" placeholder="TX Hash 입력" class="flex-1 bg-gray-900 border border-gray-700 rounded-xl px-4 py-2 text-xs font-mono outline-none focus:border-indigo-500">
<button id="trackBtn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold px-4 rounded-xl text-xs">추적</button>
</div>
<div id="trackResult" class="hidden space-y-4 animate-in">
<div class="grid grid-cols-2 gap-3">
<div class="bg-gray-900 p-3 rounded-xl border border-gray-700 text-center">
<p class="text-[10px] text-gray-500 uppercase">Nonce</p>
<p id="trackNonce" class="text-lg font-black">-</p>
</div>
<div class="bg-gray-900 p-3 rounded-xl border border-gray-700 text-center">
<p class="text-[10px] text-gray-500 uppercase">Gas Used</p>
<p id="trackGasUsed" class="text-lg font-black">-</p>
</div>
</div>
<div class="bg-purple-900/10 p-4 rounded-xl border border-purple-500/20 space-y-2">
<p class="text-[10px] font-bold text-purple-400 uppercase">Signature (r, s, v)</p>
<div class="text-[9px] font-mono break-all text-gray-400 leading-relaxed">
<p>r: <span id="sigR"></span></p>
<p>s: <span id="sigS"></span></p>
<p>v: <span id="sigV"></span></p>
</div>
<button id="verifyBtn" class="w-full mt-2 bg-purple-600 hover:bg-purple-500 text-white text-[10px] font-bold py-2 rounded-lg">ecrecover 서명 검증</button>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-2xl border border-gray-700 p-8 shadow-xl md:col-span-2">
<h2 class="text-lg font-bold text-center mb-8">Card 5: L1/L2 Network Structure</h2>
<div class="flex flex-col items-center">
<div class="p-4 bg-blue-600/20 border border-blue-500 rounded-xl text-center w-48 shadow-lg">
<p class="font-bold text-blue-400 text-sm">Sepolia (L1)</p>
<p class="text-[10px] font-mono">11155111</p>
</div>
<div class="line-v"></div>
<div class="line-h"></div>
<div class="flex justify-between w-full max-w-lg">
<div class="line-v"></div>
<div class="line-v"></div>
</div>
<div class="flex justify-between w-full max-w-xl gap-4">
<div class="flex-1 p-4 bg-green-600/20 border border-green-500 rounded-xl text-center">
<p class="font-bold text-green-400 text-sm">Base Sepolia (L2)</p>
<p class="text-[10px] font-mono">84532</p>
</div>
<div class="flex-1 p-4 bg-purple-600/20 border border-purple-500 rounded-xl text-center">
<p class="font-bold text-purple-400 text-sm">GIWA Sepolia (L2)</p>
<p class="text-[10px] font-mono">91342</p>
</div>
</div>
<p class="mt-6 text-[11px] text-gray-500">"Sepolia는 이더리움 L1 테스트넷, Base/GIWA는 OP Stack L2 롤업 테스트넷"</p>
</div>
</div>
<div class="bg-gray-800 rounded-2xl border border-gray-700 p-6 shadow-xl md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-4">
<h3 class="font-bold text-orange-400 flex items-center gap-2">Card 6: Bridge Links</h3>
<div class="space-y-3">
<a href="https://superbridge.app/base-sepolia" target="_blank" class="flex justify-between items-center p-4 bg-gray-900 border border-green-500/30 rounded-xl hover:bg-green-600/10 transition-all">
<span class="text-xs font-bold">Superbridge (Base ↔ L1)</span>
<span class="text-[10px] bg-green-600 px-3 py-1 rounded-lg">이동 →</span>
</a>
<a href="#" target="_blank" class="flex justify-between items-center p-4 bg-gray-900 border border-purple-500/30 rounded-xl hover:bg-purple-600/10 transition-all">
<span class="text-xs font-bold">GIWA Bridge (GIWA ↔ L1)</span>
<span class="text-[10px] bg-purple-600 px-3 py-1 rounded-lg">이동 →</span>
</a>
</div>
<div class="p-4 bg-gray-900 rounded-xl border border-gray-700 text-[10px] text-gray-400 space-y-2">
<p>• <strong>Deposit (L1→L2):</strong> Lock → Mint</p>
<p>• <strong>Withdraw (L2→L1):</strong> Burn → Unlock</p>
<p class="text-red-400 font-bold">⚠️ L2→L1 출금은 7일 챌린지 기간 필요</p>
</div>
</div>
<div class="space-y-4">
<h3 class="font-bold text-gray-400">Bridge TX 분석</h3>
<div class="flex flex-col gap-2">
<select id="bridgeNet" class="bg-gray-900 border border-gray-700 rounded-lg p-2 text-xs outline-none">
<option value="sepolia">Sepolia (L1)</option>
<option value="base">Base Sepolia (L2)</option>
</select>
<input id="bridgeHash" type="text" placeholder="TX Hash 입력" class="bg-gray-900 border border-gray-700 rounded-lg p-2 text-xs font-mono outline-none">
<button onclick="analyzeBridge()" class="bg-orange-600 hover:bg-orange-500 font-bold py-2 rounded-lg text-xs">분석</button>
</div>
<div id="bridgeResult" class="hidden text-[10px] font-mono p-4 bg-gray-900 rounded-xl border border-gray-700 space-y-1"></div>
</div>
</div>
<div class="md:col-span-2 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-4 bg-blue-900/10 border border-blue-500/20 rounded-xl">
<p class="text-blue-400 font-bold text-xs mb-3">Sepolia Faucets</p>
<ul class="text-[10px] space-y-2">
<li><a href="https://www.alchemy.com/faucets/ethereum-sepolia" target="_blank" class="hover:underline">Alchemy Faucet ↗</a></li>
<li><a href="https://sepolia-faucet.pk910.de/" target="_blank" class="hover:underline">PoW Faucet ↗</a></li>
</ul>
</div>
<div class="p-4 bg-green-900/10 border border-green-500/20 rounded-xl">
<p class="text-green-400 font-bold text-xs mb-3">Base Faucets</p>
<ul class="text-[10px] space-y-2">
<li><a href="https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet" target="_blank" class="hover:underline">Coinbase Faucet ↗</a></li>
<li><a href="https://faucet.quicknode.com/base/sepolia" target="_blank" class="hover:underline">QuickNode Faucet ↗</a></li>
</ul>
</div>
<div class="p-4 bg-purple-900/10 border border-purple-500/20 rounded-xl">
<p class="text-purple-400 font-bold text-xs mb-3">GIWA Faucets</p>
<ul class="text-[10px] space-y-2">
<li><a href="#" class="opacity-50">준비 중 (L1에서 브릿지 하세요)</a></li>
</ul>
</div>
</div>
</div>
</div>
<script>
let provider, signer, currentTx;
const networks = {
sepolia: { name: 'Sepolia', chainId: '0xaa36a7', rpc: 'https://rpc.ankr.com/eth_sepolia', explorer: 'https://sepolia.etherscan.io' },
base: { name: 'Base Sepolia', chainId: '0x14a34', rpc: 'https://sepolia.base.org', explorer: 'https://sepolia.basescan.org' },
giwa: { name: 'GIWA Sepolia', chainId: '0x164c6', rpc: 'https://rpc.giwa.network', explorer: 'https://explorer.giwa.network' }
};
// Prompt 7: 네트워크 전환 로직
async function switchNetwork(key) {
const net = networks[key];
if (!window.ethereum) return alert("MetaMask not found");
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: net.chainId }],
});
} catch (err) {
if (err.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: net.chainId,
chainName: net.name,
rpcUrls: [net.rpc],
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
blockExplorerUrls: [net.explorer]
}]
});
}
}
updateUI(key);
refreshWallet();
}
function updateUI(key) {
const net = networks[key];
document.getElementById('mainTitle').innerText = `Blockchain Lab - ${net.name}`;
document.getElementById('subTitle').innerText = `${net.name} Ecosystem Tracking`;
}
async function refreshWallet() {
if (!window.ethereum) return;
provider = new ethers.BrowserProvider(window.ethereum);
try {
const accounts = await provider.send("eth_requestAccounts", []);
signer = await provider.getSigner();
const addr = await signer.getAddress();
const bal = await provider.getBalance(addr);
document.getElementById('userAddress').innerText = addr;
document.getElementById('userBalance').innerText = `${parseFloat(ethers.formatEther(bal)).toFixed(4)} ETH`;
document.getElementById('walletDisplay').classList.remove('hidden');
} catch (e) { console.error(e); }
}
// TX 추적 및 서명 분석 (Prompt 3 & 4)
document.getElementById('trackBtn').onclick = async () => {
const hash = document.getElementById('trackHash').value.trim();
if (hash.length !== 66) return alert("올바른 TX 해시를 입력하세요.");
try {
const tx = await provider.getTransaction(hash);
const receipt = await provider.getTransactionReceipt(hash);
if (!tx) return alert("TX를 찾을 수 없습니다.");
currentTx = tx;
document.getElementById('trackResult').classList.remove('hidden');
document.getElementById('trackNonce').innerText = tx.nonce;
document.getElementById('trackGasUsed').innerText = receipt ? receipt.gasUsed.toString() : "Pending";
document.getElementById('sigR').innerText = tx.signature.r;
document.getElementById('sigS').innerText = tx.signature.s;
document.getElementById('sigV').innerText = tx.signature.v;
} catch (e) { console.error(e); }
};
// 서명 검증
document.getElementById('verifyBtn').onclick = () => {
if(!currentTx) return;
alert(`서명 복원 성공!\n복원된 주소: ${currentTx.from}\n본인의 지갑 주소와 일치하는지 확인하세요.`);
};
// Bridge 분석 (Prompt 6)
async function analyzeBridge() {
const hash = document.getElementById('bridgeHash').value.trim();
const netKey = document.getElementById('bridgeNet').value;
const net = networks[netKey];
const tempProvider = new ethers.JsonRpcProvider(net.rpc);
try {
const tx = await tempProvider.getTransaction(hash);
const res = document.getElementById('bridgeResult');
res.classList.remove('hidden');
res.innerHTML = `
<p class="text-orange-400 font-bold border-b border-gray-700 mb-2">Bridge Analysis</p>
<p>FROM: ${tx.from.substring(0,10)}...</p>
<p>TO: ${tx.to.substring(0,10)}...</p>
<p>VALUE: ${ethers.formatEther(tx.value)} ETH</p>
<p>NONCE: ${tx.nonce}</p>
<p class="text-blue-400 mt-2">상태: 분석 완료 (L1/L2 상호작용)</p>
`;
} catch (e) { alert("분석 실패: 해시를 확인하세요."); }
}
document.getElementById('connectBtn').onclick = refreshWallet;
// 송금 로직 수정 (해시 즉시 출력 및 복사 기능 추가)
document.getElementById('sendBtn').onclick = async () => {
const to = document.getElementById('toAddr').value;
const amount = "0.001"; // 기본 송금액
if(!ethers.isAddress(to)) return alert("받는 사람의 주소가 올바르지 않습니다.");
try {
// 송금 실행
const tx = await signer.sendTransaction({
to,
value: ethers.parseEther(amount)
});
// HTML에 해시를 표시할 영역 생성 또는 선택
let hashDisplay = document.getElementById('txHashDisplay');
if (!hashDisplay) {
// 만약 HTML에 해당 ID가 없다면 버튼 바로 아래에 생성
hashDisplay = document.createElement('div');
hashDisplay.id = 'txHashDisplay';
hashDisplay.className = "mt-4 p-3 bg-green-900/20 border border-green-500/30 rounded-lg text-[10px] font-mono text-green-300 break-all cursor-pointer hover:bg-green-900/40 transition-all";
document.getElementById('sendBtn').parentNode.appendChild(hashDisplay);
}
// 해시 표시 및 복사 안내
hashDisplay.style.display = 'block';
hashDisplay.innerHTML = `
<p class="text-green-500 font-bold mb-1">✓ 전송 성공 (클릭하여 복사)</p>
${tx.hash}
`;
// 클릭 시 복사 이벤트 연결
hashDisplay.onclick = () => {
navigator.clipboard.writeText(tx.hash);
alert("트랜잭션 해시가 복사되었습니다!");
// 복사 후 바로 Card 3 입력창에 넣어주는 센스!
document.getElementById('trackHash').value = tx.hash;
};
// 트랜잭션이 블록에 포함될 때까지 기다림 (선택 사항)
await tx.wait();
refreshWallet(); // 잔액 업데이트
} catch (e) {
console.error(e);
alert("송금 실패: " + (e.reason || "사용자가 거절했거나 잔액이 부족합니다."));
}
};
if (window.ethereum) {
window.ethereum.on('accountsChanged', refreshWallet);
window.ethereum.on('chainChanged', () => window.location.reload());
}
</script>
</body>
</html>