diff --git a/week-05/dev/frontend/.gitignore b/week-05/dev/frontend/.gitignore
new file mode 100644
index 0000000..5ef6a52
--- /dev/null
+++ b/week-05/dev/frontend/.gitignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/week-05/dev/frontend/README.md b/week-05/dev/frontend/README.md
new file mode 100644
index 0000000..e215bc4
--- /dev/null
+++ b/week-05/dev/frontend/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/week-05/dev/frontend/app/components/BalanceCard.tsx b/week-05/dev/frontend/app/components/BalanceCard.tsx
new file mode 100644
index 0000000..8821863
--- /dev/null
+++ b/week-05/dev/frontend/app/components/BalanceCard.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import { useAccount, useBalance } from "wagmi";
+
+export function BalanceCard() {
+ const { address, isConnected } = useAccount();
+ const { data: balance } = useBalance({ address });
+
+ return (
+
+
내 잔액
+
+ {isConnected && balance
+ ? `${parseFloat(balance.formatted).toFixed(4)} ${balance.symbol}`
+ : "— ETH"}
+
+
+ {isConnected && address ? address : "지갑을 연결해주세요"}
+
+
+ );
+}
diff --git a/week-05/dev/frontend/app/components/CustomConnectButton.tsx b/week-05/dev/frontend/app/components/CustomConnectButton.tsx
new file mode 100644
index 0000000..add84cb
--- /dev/null
+++ b/week-05/dev/frontend/app/components/CustomConnectButton.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import { ConnectButton } from "@rainbow-me/rainbowkit";
+import { useDisconnect } from "wagmi";
+
+export function CustomConnectButton() {
+ const { disconnect } = useDisconnect();
+
+ return (
+
+ {({ account, chain, openConnectModal, openChainModal, mounted }) => {
+ const connected = mounted && account && chain;
+
+ return (
+
+ {connected ? (
+ <>
+
+ {chain.unsupported ? "네트워크 변경 필요" : chain.name}
+
+
+ {
+ if (window.confirm("연결을 해제하겠습니까?")) {
+ disconnect();
+ }
+ }}
+ className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
+ >
+ {account.displayName}
+
+ >
+ ) : (
+
+ 지갑 연결
+
+ )}
+
+ );
+ }}
+
+ );
+}
diff --git a/week-05/dev/frontend/app/components/Footer.tsx b/week-05/dev/frontend/app/components/Footer.tsx
new file mode 100644
index 0000000..9f3d0e7
--- /dev/null
+++ b/week-05/dev/frontend/app/components/Footer.tsx
@@ -0,0 +1,11 @@
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/week-05/dev/frontend/app/components/Header.tsx b/week-05/dev/frontend/app/components/Header.tsx
new file mode 100644
index 0000000..f8c8dd6
--- /dev/null
+++ b/week-05/dev/frontend/app/components/Header.tsx
@@ -0,0 +1,14 @@
+import { CustomConnectButton } from "./CustomConnectButton";
+
+export function Header() {
+ return (
+
+
+
+ Week 5 dApp
+
+
+
+
+ );
+}
diff --git a/week-05/dev/frontend/app/components/SendEth.tsx b/week-05/dev/frontend/app/components/SendEth.tsx
new file mode 100644
index 0000000..5fdae66
--- /dev/null
+++ b/week-05/dev/frontend/app/components/SendEth.tsx
@@ -0,0 +1,98 @@
+'use client';
+
+import { useState } from 'react';
+import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi';
+import { parseEther } from 'viem';
+
+export function SendEth() {
+ const [to, setTo] = useState('');
+ const [amount, setAmount] = useState('');
+
+ const { sendTransaction, data: hash, isPending } = useSendTransaction();
+
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ sendTransaction({ to: to as `0x${string}`, value: parseEther(amount) });
+ };
+
+ return (
+
+
+ ETH 전송
+
+
+
+
+ {/* 트랜잭션 상태 표시 */}
+
+ {hash && (
+
+ )}
+
+ {isConfirming && (
+
+ 트랜잭션 확인 중...
+
+ )}
+
+ {isSuccess && (
+
+ 트랜잭션 완료!
+
+ )}
+
+
+ );
+}
diff --git a/week-05/dev/frontend/app/favicon.ico b/week-05/dev/frontend/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/week-05/dev/frontend/app/favicon.ico differ
diff --git a/week-05/dev/frontend/app/globals.css b/week-05/dev/frontend/app/globals.css
new file mode 100644
index 0000000..a2dc41e
--- /dev/null
+++ b/week-05/dev/frontend/app/globals.css
@@ -0,0 +1,26 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
diff --git a/week-05/dev/frontend/app/layout.tsx b/week-05/dev/frontend/app/layout.tsx
new file mode 100644
index 0000000..91ac395
--- /dev/null
+++ b/week-05/dev/frontend/app/layout.tsx
@@ -0,0 +1,35 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import { Providers } from "./providers";
+import "./globals.css";
+
+const geistSans = Geist({
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+ title: "Week 5 dApp",
+ description: "RainbowKit 지갑 연결 실습",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/week-05/dev/frontend/app/page.tsx b/week-05/dev/frontend/app/page.tsx
new file mode 100644
index 0000000..23d4dc8
--- /dev/null
+++ b/week-05/dev/frontend/app/page.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import Image from "next/image";
+import { Header } from "./components/Header";
+import { BalanceCard } from "./components/BalanceCard";
+import { SendEth } from "./components/SendEth";
+import { Footer } from "./components/Footer";
+
+export default function Home() {
+ return (
+
+ );
+}
diff --git a/week-05/dev/frontend/app/providers.tsx b/week-05/dev/frontend/app/providers.tsx
new file mode 100644
index 0000000..171129a
--- /dev/null
+++ b/week-05/dev/frontend/app/providers.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { WagmiProvider } from "wagmi";
+import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
+import { config } from "@/config/wagmi";
+import "@rainbow-me/rainbowkit/styles.css";
+
+const queryClient = new QueryClient();
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setMounted(true);
+ }, []);
+
+ if (!mounted) return null;
+
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/week-05/dev/frontend/config/wagmi.ts b/week-05/dev/frontend/config/wagmi.ts
new file mode 100644
index 0000000..bfb8e97
--- /dev/null
+++ b/week-05/dev/frontend/config/wagmi.ts
@@ -0,0 +1,12 @@
+import { getDefaultConfig } from '@rainbow-me/rainbowkit';
+import { sepolia } from 'wagmi/chains';
+
+// WalletConnect Cloud에서 발급받은 Project ID로 교체하세요
+// https://cloud.walletconnect.com
+const WALLETCONNECT_PROJECT_ID = 'YOUR_PROJECT_ID';
+
+export const config = getDefaultConfig({
+ appName: 'Week 5 dApp',
+ projectId: WALLETCONNECT_PROJECT_ID,
+ chains: [sepolia],
+});
diff --git a/week-05/dev/frontend/eslint.config.mjs b/week-05/dev/frontend/eslint.config.mjs
new file mode 100644
index 0000000..05e726d
--- /dev/null
+++ b/week-05/dev/frontend/eslint.config.mjs
@@ -0,0 +1,18 @@
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+import nextTs from "eslint-config-next/typescript";
+
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ ...nextTs,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
+
+export default eslintConfig;
diff --git a/week-05/dev/frontend/next.config.ts b/week-05/dev/frontend/next.config.ts
new file mode 100644
index 0000000..e9ffa30
--- /dev/null
+++ b/week-05/dev/frontend/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+};
+
+export default nextConfig;
diff --git a/week-05/dev/frontend/package.json b/week-05/dev/frontend/package.json
new file mode 100644
index 0000000..1b5afe8
--- /dev/null
+++ b/week-05/dev/frontend/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "frontend",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint"
+ },
+ "dependencies": {
+ "@rainbow-me/rainbowkit": "^2.2.10",
+ "@tanstack/react-query": "^5.90.21",
+ "next": "16.1.6",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "viem": "^2.47.0",
+ "wagmi": "^2.19.5"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.1.6",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+}
diff --git a/week-05/dev/frontend/postcss.config.mjs b/week-05/dev/frontend/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/week-05/dev/frontend/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/week-05/dev/frontend/public/Pelican.png b/week-05/dev/frontend/public/Pelican.png
new file mode 100644
index 0000000..35e628a
Binary files /dev/null and b/week-05/dev/frontend/public/Pelican.png differ
diff --git a/week-05/dev/frontend/public/file.svg b/week-05/dev/frontend/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/week-05/dev/frontend/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/week-05/dev/frontend/public/globe.svg b/week-05/dev/frontend/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/week-05/dev/frontend/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/week-05/dev/frontend/public/next.svg b/week-05/dev/frontend/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/week-05/dev/frontend/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/week-05/dev/frontend/public/vercel.svg b/week-05/dev/frontend/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/week-05/dev/frontend/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/week-05/dev/frontend/public/window.svg b/week-05/dev/frontend/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/week-05/dev/frontend/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/week-05/dev/frontend/tsconfig.json b/week-05/dev/frontend/tsconfig.json
new file mode 100644
index 0000000..3a13f90
--- /dev/null
+++ b/week-05/dev/frontend/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/week-05/dev/frontend/types/css.d.ts b/week-05/dev/frontend/types/css.d.ts
new file mode 100644
index 0000000..35306c6
--- /dev/null
+++ b/week-05/dev/frontend/types/css.d.ts
@@ -0,0 +1 @@
+declare module '*.css';
diff --git a/week-05/theory/quiz-05-solution.md b/week-05/theory/quiz-05-solution.md
new file mode 100644
index 0000000..0991878
--- /dev/null
+++ b/week-05/theory/quiz-05-solution.md
@@ -0,0 +1,491 @@
+# Week 5 Quiz: PoS/Consensus + RainbowKit
+
+> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요.
+> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다.
+
+---
+
+## 문제 1: PoS 개념 (객관식)
+
+이더리움이 PoW(작업 증명)에서 PoS(지분 증명)로 전환한 **가장 주요한 이유**는 무엇인가요?
+
+**보기:**
+A) 트랜잭션 처리 속도를 10배 이상 높이기 위해
+B) 에너지 소비를 99.95% 이상 줄이고 환경 친화적으로 만들기 위해
+C) 블록 크기를 늘려서 더 많은 데이터를 저장하기 위해
+D) 채굴 장비 없이도 누구나 블록을 생성할 수 있게 하기 위해
+
+**답변:**
+
+
+
+B, POW에서 블록을 생성하기 위해 채굴자들이 해시 연산 경쟁을 해야하며, 이 과정에서 막대한 컴퓨팅 파워와 전력을 소비한다. 그래서 POS를 선택하여 검증자는 자신의 ETH 를 예치하고 검증자가 블록을 만들고 보상을 받아가는 구조로 바꿨다.
+
+---
+
+## 문제 2: 검증자 역할 (객관식)
+
+이더리움 PoS에서 검증자(Validator)가 수행하는 **두 가지 주요 역할**은 무엇인가요?
+
+**보기:**
+A) 블록 채굴(Mining)과 가스 가격 결정
+B) 블록 제안(Proposing)과 블록 증명(Attesting)
+C) 트랜잭션 전송과 수수료 수집
+D) 스마트 컨트랙트 배포와 실행
+
+**답변:**
+
+
+
+B, validator은 블록 제안자로 선정되면 원하는 트랜잭션을 블록에 넣어 네트워크에 전파하고
+다른 validator들은 해당 블록에 투표를 한다. 이를통해 블록을 justified 한 후에 finalized한다.
+
+---
+
+## 문제 3: 왜 PoW에서 PoS로? (단답형)
+
+PoW(작업 증명)와 PoS(지분 증명)의 **핵심 차이점**은 무엇인가요?
+"자격 증명 방식"과 "보안 보장 방식" 두 관점에서 각각 비교하세요.
+
+**답변:**
+
+
+
+자격증명 방식:
+
+- PoW: 블록을 생성할 자격은 해시 연산을 통해 타겟넘버보다 낮은 값을 얻은 채굴자에게 주어진다.
+- PoS: 블록을 생성한 자격은 네트워크에 ETH를 스테이킹한 검증자 중에서 프로토콜에 의해 선택된 벨리데이터에게 주어진다.
+
+보안 보장 방식
+
+- Pow: CanonicalChain을 따르므로 네트워크를 공격하려면 전체 해시 파워의 과반수를 확보해야한다. 따라서 막대한 컴퓨팅 파워가 필요하다. 이 비용이 안전을 보장한다.
+- PoS: 검증자는 자신의 ETH를 스테이킹하여 잘못된 블록을 제안하거나 부정한 투표를 하면 슬레싱으로 스테이킹한 ETH를 잃게된다. 따라서 경제적 패널티구조로 네트워크 보안을 보장한다.
+
+---
+
+## 문제 4: 슬래싱의 목적 (단답형)
+
+슬래싱(Slashing)은 검증자의 스테이킹된 ETH를 **강제로 소각**하는 패널티입니다.
+
+1. 슬래싱이 발동되는 **두 가지 조건**은 무엇인가요?
+2. **왜** 이런 처벌이 필요한가요? 없다면 어떤 문제가 생길 수 있나요?
+
+**답변:**
+
+
+
+1. 슬래싱 조건
+
+- Double Voting: 동일한 epoch에서 하나의 validator가 서로 다른 두 블록에 대해 투표하는 경우
+- Surround Voting: validator가 이전에 한 투표를 포함하거나 감싸는 형태의 새로운 투표를 하는경우
+
+2. 슬래싱이 필요한 이유
+ 슬래싱이 없으면 validator은 여러 체인에 동시 투표하는 행동을 할 수 있는데
+ 그러면 여러 포크체인이 생길 수 도있고 무엇보다 finalized가 보장되지 않는다.
+
+---
+
+## 문제 5: 체인 선택 규칙 (단답형)
+
+여러 유효한 블록이 동시에 제안되면 **포크(Fork)**가 발생합니다.
+이더리움의 LMD-GHOST(Latest Message Driven GHOST) 규칙은 어떻게 "정규 체인"을 선택하나요?
+
+1. LMD-GHOST의 기본 원리는 무엇인가요?
+2. **왜** "가장 최근 메시지"를 사용하나요? (오래된 메시지를 사용하면 어떤 문제가?)
+
+**답변:**
+
+
+
+1. 루트 블록에서 시작하여 각 포크 지점마다 가장 많은 validator의 투표가 모인 자식 블록을 선택하여 내려간다. 이 과정을 반복하여 가장 많은 지분의 지지를 받은 체인 경로를 정규 체인으로 선택한다.
+
+---
+
+## 문제 6: RainbowKit Provider 계층 (빈칸 채우기)
+
+다음 코드의 빈칸을 채워서 RainbowKit을 올바르게 설정하세요.
+**Provider 순서가 중요합니다!**
+
+```typescript
+'use client';
+
+// TODO: 필요한 스타일 import
+_________________________________________
+
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+import { WagmiProvider } from 'wagmi';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { config } from '@/config/wagmi';
+
+const queryClient = new QueryClient();
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {/* TODO: Provider를 올바른 순서로 중첩하세요 */}
+ <_________________ config={config}>
+ <_________________ client={queryClient}>
+ <_________________>
+ {children}
+
+
+
+
+
+ );
+}
+```
+
+**답변:**
+
+```typescript
+// 완성된 코드를 여기에 작성하세요
+// TODO: 필요한 스타일 import
+_________________________________________
+
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+import { WagmiProvider } from 'wagmi';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { config } from '@/config/wagmi';
+
+const queryClient = new QueryClient();
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {/* TODO: Provider를 올바른 순서로 중첩하세요 */}
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+```
+
+**왜 이 순서인가요:**
+
+
+
+wagmiProvider은 블록체인 네트워크 연결, wallet 상태, signer, provider등을 관리해야한다. 따라서 가장 밖에서 이것을 받아와야한다.
+QueryClientProvider은 wagmi에서 사용하는 비동기 데이터(fetch,caChing)을 관리한다.
+wagmi와 rainbowkit 모두 리액트 쿼리를 통해 데이터를 캐싱하기 때분에 wagmi 내부에서 제공된다.
+RainbowKit은 지갑 연결 UI를 제공하는 라이브러리이며 wagmi의 상태와 리액트 쿼리를 사용한다. 따라서 가장 안쪽에있어야한다.
+
+순서가 잘못되면 필요한 context를 찾지못해 오류가 발생한다.
+
+---
+
+## 문제 7: Provider 순서 버그 (취약점 찾기)
+
+다음 코드에서 **문제점**을 찾고 수정하세요:
+
+```typescript
+// BAD CODE - 문제점 찾기
+'use client';
+
+import '@rainbow-me/rainbowkit/styles.css';
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+import { WagmiProvider } from 'wagmi';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { config } from '@/config/wagmi';
+
+const queryClient = new QueryClient();
+
+export default function Providers({ children }) {
+ return (
+ // 문제가 있는 Provider 순서!
+
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+**1) 발견한 문제점:**
+
+
+
+Provider의 순서가 잘못되었다.
+Wagmi->QueryClient->RainbowKit 순으로 가야한다.
+
+**2) 왜 이것이 문제인가:**
+
+
+
+RainbowKit가 Wagmi 바깥에 있으므로 wagmi의 context (지갑 연결 상태, 체인정보 등)에 접근할 수 없다.
+실행시 런타임 에러가 발생한다.
+
+**3) 올바른 수정 방법:**
+
+```typescript
+// GOOD CODE - 수정된 버전을 작성하세요
+'use client';
+
+import '@rainbow-me/rainbowkit/styles.css';
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+import { WagmiProvider } from 'wagmi';
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { config } from '@/config/wagmi';
+
+const queryClient = new QueryClient();
+
+export default function Providers({ children }) {
+ return (
+ // 문제가 있는 Provider 순서!
+
+
+
+ {children}
+
+
+
+ );
+}
+
+```
+
+---
+
+## 문제 8: 트랜잭션 상태 처리 (빈칸 채우기)
+
+다음 코드의 빈칸을 채워서 트랜잭션 전송 후 **확인 상태를 추적**하세요:
+
+```typescript
+'use client';
+
+import { useWriteContract, _________________ } from 'wagmi';
+
+const abi = [
+ {
+ name: 'increment',
+ type: 'function',
+ stateMutability: 'nonpayable',
+ inputs: [],
+ outputs: [],
+ },
+] as const;
+
+function IncrementButton() {
+ const { writeContract, data: hash, isPending } = useWriteContract();
+
+ // TODO: 트랜잭션 확인 상태를 추적하는 hook
+ const { isLoading: isConfirming, isSuccess } = _________________({
+ _________________,
+ });
+
+ return (
+
+
+ writeContract({
+ address: '0x1234...5678',
+ abi,
+ functionName: 'increment',
+ })
+ }
+ disabled={isPending || isConfirming}
+ >
+ {isPending ? '서명 대기 중...' : isConfirming ? '확인 중...' : '증가'}
+
+
+ {isSuccess &&
트랜잭션 성공!
}
+
+ );
+}
+```
+
+**답변:**
+
+```typescript
+// 완성된 코드를 여기에 작성하세요
+use client';
+
+import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
+
+const abi = [
+ {
+ name: 'increment',
+ type: 'function',
+ stateMutability: 'nonpayable',
+ inputs: [],
+ outputs: [],
+ },
+] as const;
+
+function IncrementButton() {
+ const { writeContract, data: hash, isPending } = useWriteContract();
+
+ // TODO: 트랜잭션 확인 상태를 추적하는 hook
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
+ hash,
+ });
+
+ return (
+
+
+ writeContract({
+ address: '0x1234...5678',
+ abi,
+ functionName: 'increment',
+ })
+ }
+ disabled={isPending || isConfirming}
+ >
+ {isPending ? '서명 대기 중...' : isConfirming ? '확인 중...' : '증가'}
+
+
+ {isSuccess &&
트랜잭션 성공!
}
+
+ );
+}
+
+```
+
+**트랜잭션 상태 흐름을 설명하세요:**
+
+
+
+1. isPending 상태: 지갑서명을 대기하거나 트랜잭션을 전송하는 단계
+2. isConfirming 상태: 트랜잭션이 브로드 캐스트 된 후 블록에 포함되길 기다리고 있는지 판단하는 상태
+3. isSuccess 상태: 트랜잭션이 블록에 포함되어 receipt가 반환되었는지 판단하는 상태
+
+---
+
+## 문제 9: 검증자 생애주기 (다이어그램 해석)
+
+다음 다이어그램은 이더리움 검증자의 생애주기를 보여줍니다:
+
+```mermaid
+stateDiagram-v2
+ [*] --> Pending: 32 ETH 입금
+ Pending --> Active: 활성화 큐 대기
+ Active --> Slashed: 규칙 위반
+ Active --> Exiting: 자발적 종료
+ Exiting --> Exited: 출금 대기
+ Slashed --> Exited: 강제 퇴장
+ Exited --> [*]: ETH 출금
+```
+
+**질문:**
+
+1. **Active** 상태에서 검증자가 수행하는 주요 활동은 무엇인가요?
+
+블록 제안자로 선정되어 트랜잭션을 담은 블록을 제안하거나, 제안된 블록이 옳은지 투표를 한다.
+
+2. Active에서 **Slashed**로 전이되는 조건은 무엇인가요? 이 경우 검증자에게 어떤 일이 발생하나요?
+
+Double Voting을 하거나 Surround Voting을 하면 Slashed로 전이된다.
+이렇게되는 경우 검증자는 예치해놓은 ETH의 일부를 잃게된다.
+
+3. 검증자가 자발적으로 종료(**Exiting**)하려면 왜 바로 ETH를 출금할 수 없고 대기 기간이 필요한가요?
+
+바로 종료가 가능하면 네트워크가 위반을 발견하기 전에 ETH를 인출하게 되어 처벌을 회피할 수 있게된다.
+
+그리고 갑자기 많은 validator이 한번에 나가게되면 체인이 불안정해질 수 있다.
+
+---
+
+## 문제 10: Provider 계층 구조 (다이어그램 해석)
+
+다음 다이어그램은 RainbowKit/wagmi 앱의 Provider 구조를 보여줍니다:
+
+```mermaid
+graph TD
+ subgraph App["React App"]
+ WP["WagmiProvider config 제공"]
+ QP["QueryClientProvider 캐싱/상태관리"]
+ RP["RainbowKitProvider 지갑 UI"]
+ COMP["Components useAccount, useWriteContract 등"]
+ end
+
+ WP --> QP --> RP --> COMP
+
+ subgraph Deps["의존성"]
+ CONFIG["wagmi config"]
+ QC["QueryClient"]
+ WALLET["지갑 연결 상태"]
+ end
+
+ CONFIG -.-> WP
+ QC -.-> QP
+ WP -.-> RP
+ QP -.-> COMP
+```
+
+**질문:**
+
+1. **WagmiProvider**가 가장 바깥에 있어야 하는 이유는 무엇인가요?
+
+2. **QueryClientProvider**의 역할은 무엇인가요? 없다면 어떤 문제가 발생하나요?
+
+3. 아래 코드에서 `useAccount()` hook이 **"Cannot find WagmiContext"** 오류를 발생시키는 이유는 무엇인가요?
+
+```typescript
+// 오류 발생 코드
+
+
+ {/* WagmiProvider가 안쪽에 있음 */}
+ {/* useAccount() 호출 */}
+
+
+
+```
+
+---
+
+## 제출 전 체크리스트
+
+- [ ] 모든 문제에 답변을 작성했는가?
+- [ ] 객관식 문제: 정답 선택 **이유**를 설명했는가?
+- [ ] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가?
+- [ ] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가?
+- [ ] 다이어그램 문제: 각 질문에 논리적으로 답변했는가?