From 81c311e6ccce1c1e7ee7666bc610bf37ed3b88a3 Mon Sep 17 00:00:00 2001 From: jaesimin Date: Wed, 11 Mar 2026 02:31:31 +0900 Subject: [PATCH 1/2] feat : solve quiz and develop fe --- .gitignore | 6 + week-04/dev/my-dapp/.env.example | 6 + week-04/dev/my-dapp/assets/1-init.png | Bin 0 -> 17758 bytes week-04/dev/my-dapp/assets/2-connect.png | Bin 0 -> 30291 bytes week-04/dev/my-dapp/assets/3-increment.png | Bin 0 -> 33188 bytes week-04/dev/my-dapp/assets/4-decrement.png | Bin 0 -> 34088 bytes week-04/dev/my-dapp/index.html | 12 + week-04/dev/my-dapp/package.json | 26 ++ week-04/dev/my-dapp/src/App.css | 153 +++++++ week-04/dev/my-dapp/src/App.tsx | 30 ++ .../my-dapp/src/components/CounterActions.tsx | 59 +++ .../my-dapp/src/components/CounterDisplay.tsx | 42 ++ .../src/components/TransactionStatus.tsx | 60 +++ week-04/dev/my-dapp/src/counter.ts | 39 ++ week-04/dev/my-dapp/src/main.tsx | 10 + week-04/dev/my-dapp/src/vite-env.d.ts | 10 + week-04/dev/my-dapp/src/wagmi.ts | 13 + week-04/dev/my-dapp/tsconfig.json | 23 + week-04/dev/my-dapp/tsconfig.node.json | 23 + week-04/dev/my-dapp/vite.config.ts | 6 + week-04/dev/script/Counter.s.sol | 13 + week-04/dev/src/Counter.sol | 23 + week-04/quiz/quiz-04-solution.md | 392 ++++++++++++++++++ week-04/quiz/quiz-04.md | 14 - 24 files changed, 946 insertions(+), 14 deletions(-) create mode 100644 week-04/dev/my-dapp/.env.example create mode 100644 week-04/dev/my-dapp/assets/1-init.png create mode 100644 week-04/dev/my-dapp/assets/2-connect.png create mode 100644 week-04/dev/my-dapp/assets/3-increment.png create mode 100644 week-04/dev/my-dapp/assets/4-decrement.png create mode 100644 week-04/dev/my-dapp/index.html create mode 100644 week-04/dev/my-dapp/package.json create mode 100644 week-04/dev/my-dapp/src/App.css create mode 100644 week-04/dev/my-dapp/src/App.tsx create mode 100644 week-04/dev/my-dapp/src/components/CounterActions.tsx create mode 100644 week-04/dev/my-dapp/src/components/CounterDisplay.tsx create mode 100644 week-04/dev/my-dapp/src/components/TransactionStatus.tsx create mode 100644 week-04/dev/my-dapp/src/counter.ts create mode 100644 week-04/dev/my-dapp/src/main.tsx create mode 100644 week-04/dev/my-dapp/src/vite-env.d.ts create mode 100644 week-04/dev/my-dapp/src/wagmi.ts create mode 100644 week-04/dev/my-dapp/tsconfig.json create mode 100644 week-04/dev/my-dapp/tsconfig.node.json create mode 100644 week-04/dev/my-dapp/vite.config.ts create mode 100644 week-04/dev/script/Counter.s.sol create mode 100644 week-04/dev/src/Counter.sol create mode 100644 week-04/quiz/quiz-04-solution.md diff --git a/.gitignore b/.gitignore index ff3bed2..a250eee 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,9 @@ node_modules/ # npm package-lock.json + +# Vite +dist +*.local + +lib \ No newline at end of file diff --git a/week-04/dev/my-dapp/.env.example b/week-04/dev/my-dapp/.env.example new file mode 100644 index 0000000..60be164 --- /dev/null +++ b/week-04/dev/my-dapp/.env.example @@ -0,0 +1,6 @@ +# Counter 컨트랙트 배포 주소 +# Anvil에서 배포 후 주소를 .env.local에 설정하세요 +VITE_COUNTER_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + +# WalletConnect Project ID (https://cloud.walletconnect.com 에서 발급) +VITE_WALLETCONNECT_PROJECT_ID=YOUR_PROJECT_ID diff --git a/week-04/dev/my-dapp/assets/1-init.png b/week-04/dev/my-dapp/assets/1-init.png new file mode 100644 index 0000000000000000000000000000000000000000..bf64e86e9f4da72ea675a0b69d911ccc6a95fc5f GIT binary patch literal 17758 zcmeHvcTiK?8ZRJHr6?kY-kV5~-iv_rBE6$h10+!b1PC1k6{Hs_0i+5DC{;=T1*LZc z=}HTs3Isw8Z*%TB_uhH$uQ&5%-g`5Lne6Ojt@W+FzViFZTH&R!p%xVd69oYQ0hNxn zh6w=ykv0JVVHz0;@J-~GATaPj3#6`YtfQ{ZVeAcc1$ns;5C|j&C$#Bx7}IvOfrRL1 zK7StJjESV>HN*V05Sq9bWh{&*_>7e*zIP*6=M@?Lx*`7RV%u;WL z5aM-a+1lr)xwv3>*BoKS(-@cxY5RUb-UC~UhI;t;8C|tNf>jjcV}hmrSRRA&5lSED+=wN;p6A}V`m~cb_lD6`$(y?w&iub9dn%rtc4WW4pL!JK zpXB>qdYAj-1k(ue`r)j|=^Hge$J+DNrcHHasw-0OtTdCmIcz$5*^gs7&TdnGei}hQ z`eay5LyF!+*6v5A@6p3I(f92l3?0(o_<%Mug?v=ny6QK*IwiU_mA(TLa1_M6g{0E2 zk>1MD(tOeGQ=75wr{OE#2A}&+5%1Ns|8#gUnL`_?$8oA!x2q8S!eK#JAO#>b0W!1lu`-G=%dbVbIR5P7g%J##dSH+zyJRAoG$*L|I7q|{k1G$fuff!qIX5a zME^}27^-;rt-LYF-^I&X0|W-x1I(c;DJHG>XaE1D5r(t3$!Sls?M;k<4F%|GV+8fr_G+EB`M}{EN+hehctfnL<(Y-!fCC(Bc1`M?j!T zprfH?=1;gaN7{1pJF0#60vu+d@j&oa0`;iI$2_73A09F|LGvDOzI?g!wbSj@eWx1- zD^rhm9*e$w#dYf@LX%K1KP)0YLK{5+$Jg|4_fJjDHBTZkrjQHAK_bl(FxS=Wos2&G zj+~pTp1j|tJwFdItttWG->RI633H2&3@keDpt z6Uo2(>V*;X5Iws7CmrCzp}NTQ7ga*yI{+EVXPST02_tyv`8N&P4*;1vw70JR-I<7| z@^2cd|4jG~OaD=tKRNJ^S^R@Ee{$e|153lb$!K3BtD*;IbaIuIT%uX!`>I`3y4`Yv zIcr=862F&EcD@X6;tC@mB3lLkyO*$Y^$UaSTHg7xWt)5-@qJWbVIj|ldz1_zytp~s z2fNVoitrh0>pzH`y4Xb1?GI10pI4NXCD996^o$nuEjOb1`V3^|IE5@LdmD3+pAoI2 zqh`@9Y;9Wc_J!5eqw~*Qh-n3A!;|7z5tH=V)`EOQ0pwnF<_a0IyALqqrRpf$>2`CG z_g+t10%)vw5|R73@6!eTRJX>JzQ?1&y5_z^(C+tT@05k?P<+PT%mQ9~hil^d_a23v zAP)W>wvPC?Tg>2TwQ_Wko;G?u*;4fAxLUZ;|A&;}C{6|Oa0=k*F}hcs{9p;7XQ zKF))t;pR^s1z<;|c%CjaW78T_QeOsmQ@Vym&y3?kaDFbDD&ceecOK5eSL5cJ`??dkI~77T;CDnt zlbIn$%`85{cf-3Lx%?tCvo!B(gS3tbejsUMmG{~rZo5K?P4*sha^<(j9WCLyhu@yg zWCitUu?IhcPB{$2UbBam*##dQ9P*`ISIr|J9=(7IgxxdMAQmUbZqKRv&H19+2Q2NL zzJ}g7JlzR(jwnJDIkqY9EhpppinAb#Gtd$wiS|&=T=v7>hR-!({=W&Pf^nRq6*JU) z0-k*GQ;3Q8jeGqP$%Vzm-;&2JEcjzLo}^_>ytnq;es?-+k3~%fSXS5w=Y}dT^q5*$ z@F{Hnm`Qi!XA`x-*403Mwue*$${-8h61Lt$wj4|}4-UUo;tWVOu#ts01A;BqOe!M8Du3MF;C6Ptqi+1Wf6rJ_KTlLQBA{yWeJ3_WCXo>+!R&p#<2V+}!c7 z&WyW-ndM}+P?dga&0v!lc(jUosULaT3T>F4wwdyq(V$(N%M1U^pWCu+&pXXs9gM~g zHjNZ$WzZa7?1KR&7gg^May#!W^Izeo^_&uE-h{T2cV3(?FmzvKIQs(6)~8eo+|0Hp z(Gxj-ogKKo3sNga#IlFlaZcL7B6qU4>qE~snB=g-SLQU)zN3Zd&c}Q7#RJqvl^3g7 zynM!gB-&92(n*r)M1FQaMG?EeC(h9-bg`3pwObOd{YeiBA`ma}XXuy}?3mHv*8aHZ zko;%;atq<$4N~e^J)gZ$@6*0U)gRX%{l5 zu4#H>>9E&d!eb{v22;3Gk{hBOf@`LfAkoTnL_EG9$0EWWa#S{^zM}N)oBzeQDGf1+ z7Gw&KEs0sdwOt$-iM*nAG0Zu8mK}TuVvOWgdU@!uCdaS;sloxh`Qu}xsBGD!AyvQ6 zk4OA3NF4pvXBsTOy`5I4VFkO%gs-FazA-~fDkk%U7Yd8jCac-Bq9fxK_h&PpcqskuY6&}Q3wU6g!hGWb)U z`KPsfJxv5*i%PuGVM_B<01BOxmHmr)q4y4@4I7n3?WDU?k#K_-`WkFSej&pxE_FZ# znk8gAu|=c!q#V1o!M@m6-|_nwqSxH={I2aSx2zC%==(w?aU;cD{b`I3bs{x*N{4?3 ziaJPZ{q8wgNyl1^+8-nh+VJHRc%$35Z2fktM7_Q-iOD`_x60ck+0;-@+KmmVAF_jz zLHBEs5HIqmhTSZg5ZP;*-nW`vUnjK;9?gQbdPa4IY@P0g{KSY2e|V&Rg{ElV#Rs3L z%++ov--fPihSXX@Q(S_L--h^;@@}noM}PN$#0=Wnv>avSy&sgGtAs_)Mzfbi54wF! zp1)ny5hL1mmMH=KHXkyo)AfQHw3;mWqO`)GVF3Nd)L!e!I&*lIuShCVr$b(O{(}OXAt_hb89!`YF6KBC9c&Y4_$n)G1&|qbMO_J&p~Xb z)#uQYb()6}2v!O^K06r1TH?k_q0+kG(Br-uqIlTZuG^cJqlh;>(*^w%bCCmdnU=*X z%OWaLT(9jW=H z-M6J?Vj7Diy*fwA_6FiJq(sBlw>P4N8A|lNGU8ku*fK)CsD#*Fto;>o%F5_(4@yA% zc!cQ)KKvy`FmX8ZC|a$0%&=p5DcTQo0F$HQ)TJbp?jojZ zf6_z-=}UgXd@n~#i{R!@6UJ;8A;a^TPH~Ls6Ul|4U*nnK`B@b`j$QF>?QryuNE&56 zrj{Q)t#p0!IUu>GleUw!0|ob4bFSt26p)ybvOF=fQAW)O+m7ZWpX1n*6A!^$kNU%Sf)+(0;KoX^6<#z7>mBqKmQork}} zC>LKE%LwzH_AMf1mRKEr-!cjrvI;sqrxw@`piAf&b%mqpjhnI8*c8Qtp{DPLf4xKJAntAr zi=rfX!AM-%@7mYa7S4O0FvXtBBU(F$=XUTpbC6cA6u;j2?9AAhA0&ca{#bdfbmLIU zcRpZgY_M`@!UQIRX_4BguAI#44K|C&5}nyNW;l}u*UX!jgeM`ruQr|tP$J%#M}Ah= zX<1NC$>`{c6dp6Sx^5dj9%=PhPxWhzCdZnHWW@~N%<#!=w!-d5hR}w|MlCfE{_Hsj zezgwT=4(80-;rTlB9A}d`~cp+HS5H*5@g@(N39q~%{##liqD*%N-KSTBE`xqfw3GT z<~?OpJ7M8ez-R=awidEBN#ob0SzQ&jYWG{Fh!#Abq+)Dx?k<_4D?wJkH) z;Nq(O5jptbho{Bphf`h7jN!6mv>adGraGB)iMCqNT8(k4`%&0ItHyKqP{Gq#KFTi# zhd%Duj4GB7qm#H)Rk+V-bvg|ruK|+Gv-HWluXw0NP;2Oxo2>LRa-~weLOJjMFhtPFjZml?Nh35{U^9me8@}71!IYAcgPm03dC|_5! z?Ug?JLy~5YO}Qq%B9x=$XtDfoy9)c;d9C$W<9{hdG z?R;&lxa+mTWW#)Oo)UptLrz=GFClTPki=$T$Mrx;_^@eGgM{PtM-8uoH(u?S{Fj_A1JigOx193OnMbvlt~8 z1lBT4g)9n<>JQUAcTmwJB-)70L}ivTxJnWb0duzVfGeNn-}v+^YKCY7)=qeuNxI6) z@W~+e!9I4Zbfy-S!4`~N7|f^EB34>jD=Wt@4{WzgzP(A6y@{Q$-H|KDisJK}UNgfZ zP%!~~V<6DR7pswJMeX6NVk0BmaE`*c$cH>^mkQouyLo4IIsR}$lM`e4%?`0eAFesV z@6@c5d+Z!KlnEKD!VJqPtEs3dr5#XJLmGL5_u3pRgHNzNF)2X9?_W<}iFi{qfGhqRr`pBfL^8vq$p zLXCrptgkojmaW%zUkUpc8kMucv5q`P=W_O0H6!oCt?_QkQckgpa3keot6h@Z^}A!& zRHhwMPB&}fWruV}Mf-ino-^ctTfgIP!bbwA)_wO~+&izb7LQAh7ApB(M)``RHqp)E zRBj-ej1a;1S>JMhiMCZv%C8lWiSq_W0glyh4?Mx4H)D;ibI}WARod&eU)^{pLWi`K zaqWM!8rP$D1s?GGGq#JuUt(R3-yradBIO2dutTA4-qJBR;HNzzgU<&M--RGvyoCMq zw73UK2Qe}#XHRse9na)_-WpL<4vj_BMR(!FhlQ$IEU@KLo7nU=JFZZ1B_8wd-@}vr zr7kE%TQ(8O!<$pNMM}?YRJz#$*Qoj5P&85YDnm{0c>XT_93R}*98&b0S`#xDu=EXy zzNU13P&HW`tn~bl%OLA8bH5YJYO*;}P`+mA_5+iH;QLXY>1AzS_j)}GyWN?4aW?Mk zcTSO|rcgxV3&dl(;zq;Fs4IZ{#+naWblAVW5mFsJ3q-c=>E71fXX^;Aq8ZDU#JIrk zE|m`wHecXv#UXGR|Hy}M)~ZX7&RPGdKIr-+Vyc?GpYDz56Skj|!yfCFaqiF*8O)@c z^T|UY86{;kxGV;L!YLPWCW?v{4>w+IJL#Z^n#g8J)yd63c64qw(r-qa+b5aLcWlz(46H@+CotZZX+f70o=mz zgHmI8<#$I{(jAe9{k*52)H)jd*z9xU4=UONjMM_GqyO!)-WMGHxW+7PvxkD|t8f^} zt!&1I4^O#W@QDd@YrLmX9Itx$B$s4sb?j}e!uF>j`~IVo_ABwOvlC{_BA?nX^mDz? zBUMBhiUGRycUr-LxS4kkw3g3|aa*;Cx3GRkpChCBqs`~WNCxc3N=%0`r6vcGcBS6E zmI@os6EYobK36_!SFu(Rs8UARvJ+(p*~122H&)9oe3tR^I=tOxhS6=wPe4S%M`^u!dRH{j z+6S8qe~dUB^Y#>b-w`%V5{N^F@ahiZ)~?Rkv zIAW3{`JRjQ@~Wyyb^e3AOk6fhgu=_nci5r*tvb%glfg0EN|hsuh?iC2(C{3}tWHqI z!cK_1S&{q-aj?$0H4TO+KP&I2i(z=7d`;Z;L$ZN(d}DG(ZhnW+$)o)NnT5=p=W+p| z22t||#Y2lDRrq0=@A@>meHLlOKZ+aLXqctPGlt9^93blro%(Es7q_W!vo`VJS5D>F zQd7;v5r=jqT6~i=fwSjJhw_0~0rd5o^MRf;qhW*yxpE%?!?yhUQ2Alpbl<@|f}MoK zu&EHGI~SUzIZg#Y$~q-YtkBOp`?V!6Ws6qA^ZI4r|b zpgP`HwU622&&WEh0sE0{Q1;dOYnMS4$W>HtS^Qae;s{gCz6wNHvnS)Mf7ShouKF80 zb5IHaTLR(548v8o16CO?#_D~OLe-NE^Ki8tP3U z)|r<4;u_iIa{y%@8DOSQb+QMVf6bf?_;@N zMYHxnJ$I4o2B*nPee<{!l>Y`=w*7a?d&k>FeJaV-(1p~!Dn30-?=kcAd|ix$apD=1 zF%>bb`v+B>t~k`(1r>7;t)B*m4N<9HQ?WTi={mgF46ZC@qZ#`U)XSis@(LrPH&u3= zGkTpTK+Hde00=AqjeFq45Nd83o;XogwvkvJlCHNjXPN>nVE(eYb8dZGIdpb9S@mBV zKdMb zueoN3N@<|y##P&Xg=W;ULxY{(Y((oNal|?Sd5QFd#IJOLe(y3R=2J&#V9405Yvo_F z@*j9q#jRs#l9*P8S%?8^deH)a^K#Y;B3A}&EL$V{qCBU<1iZwhs~1|%2!SaMI8;^z zq%Yje1i4JciQF9J2@+i`xe zzQVKfubK9V4YZhaPKm)j<1C--AFIe1~VApBY)0}K32~GccN7k)bh^4Z@$&c@JHR9-?BscRf!C4-+P7mC6ftf zQo6@-$9s3-W+bb=mT4I(mVtGmiBo#(g~b#0S^npJk5=~fKf8!8ZOBh8K{t@8mTV3p z&A#M#W^|`Qx$|zk@8ULYH(%a#yE6pOJ?YfrZI0_qpYu?#V%~TvTYU*>&pA}X%*ZM< z7&kbFf6Lt^#fc_+hUOew{=&mOmg9{qOHzM@vFfd0l_IYc(r>J169|G!!sw^__Kz|K z3LQ%{KD;IDZrRyz?v&bac|jglJL&P;4--DjSv)%fz0Q&syJ>nmOInZEj|2&IyjNJ)G)dKvU6Fv&qmZ8XkxJ5dCYOugS&YCxH&)XySlV2b|U`^Ck#`fD*4 z!cQm_EQ8xYZ+8d*N)o2(8jcM)?GKoWP<19AU1gUt>5&Jgk@49`4~f2$4Zw*XfBm45 zK9HMJ>KDNSO{{JC&X;5DSfOrST6;?U(ot=$@^c~|RsIC{MRS?{tlw{{)V{O+7)yrs^mG#CBqQ0^Yzx~9kN&}PUTZa@tlkq<}SOjAUI^TJfx zwOM%F?K`M<=D`+^h$vYPZ9Yhze)P>h{O;82@*&_mc0R(U@$jozRgx_QRbT`ks7*rU z`q5gU8oe+BO;%)-iG+R=&83?{C;(#yGEgnP<*( zZ|RI4Yx^-hLJOq(qv+PbPa_%4vA^-{fSil=o51*!*7cEo02d(hkiycV<)97*k=#H~22G&7kZS zGTFK0Yu`q;Ew0TrwRK>idBc3eU^(hEltJ5m>wS_5|#^@_H926aav^pKPGDAwanNq zVJSsn7*El9#|MxVS)Q!R2rB;;fV%6eMcxP3fqZydY$A9yLyIXp_EWvdeA}lCuSeS~ z&A4%jse`h9C9&=Om_fz|e0ml~L;fEy2N-vvTzwiJAK1omu1v$}#uY9Ep7V4Lwyk_( z1A3gJFt1HBKdX3pc-vr}7hSmo_!gi9&;a-X2)Fmm#`bFx*S-gri#MKDFv;z_?aVGH zD&Hu)SlL)1-zU)8BFsE62&9P{@ScAWbs=Bp@gOPN3(Rhre()$&KK2lQFEp>1@cZ>h z-J*bi6Gh&+D$U2^#tT4=0noTTTKA)iwSeqr@5wZ*Rrw#|W}9qQgEnX)eD<*2p~X4Y zDdp5MS#oL%l~{JCvw0V5_Inu{Z?^qV11e(`c<=xVFN)G|sDiFJa#YLlYxjQsL#vLcVdP%?oetqDTtWA@6;1f>S&#k(Qd5mPq?97nuG$yZd?AtP|f;Qr`3V z8Zo*dSqjr!7B1Jo-Mj#=3v0Uf07FPj#y-T)xB0$}8QCh41*sy!z~ z&n=cC1Vk&Zj_BXKK}gJG4Fn8{YI1h|L`8k*DEJ?boSLSk_WS`L@ON9vMS zBiLTNv9dheY96`c9iD!BC3C8^yE@Z7eGqk+h?dl9vT#Yt(&THKCxHU!8zy1RS>+Wb-NzQ-#(=N5`rYoPbdBoc5PcwC8$Cm*xD9?w3#5Ek5XfgTd{z+1 z{wns;G(Q8R=0q*Uzv{)CV8=n(YR87c26AC>gjmzf!3dif&x$3|OZTo${U3`#?#>q3UU@~V+ z61-?uk&9DmLHq0MGEIaDVNxh6E9(*&^Q?fZ>{Su3lXbiqM~aYknUG*=Q<69xeM*+4 z!~vuOjaVn!CDeZ|T?%iVF3HcMDyKWn4h?QTx)l`tkTZg4HNCK~a^mdVSeUnNRcj&n zPc?xhh>w2YJRv$EL<)B5c)^GYs#jnB6v(?brLri&s_;`a(KZ$38vfPSk9=M<7<`i1 z?o=%XP97ClcOpqFRY=%#RpW8K9anZJ*HSAtvl}f(eeYO?OQ}| zVmto@rb_pFnSQ^MhwMpNMrkE4#Lc#Pa^Onu4AHH3LQ<&G)soskBzamH#V5X-tl`A~ z-#nNsCKW^(KGEW4UMGVf&WO82>B` zfBk`pDOZtHY63@Cm==&6M1G#RM?Y+|qcNF4$CGL%t4{WE+Id}E{Pai}OIf#V@L-!* zKZ*bN{?Ll8`>SMFKP|COcZDHZU?llwgmOu283E0!SdTvAO2w-;*k|R@-N!LMHj!2Z z7kh6VRQjK#8ue?TZQm_$_Lz$iogkKG+l|F|i}T1ND*)>oJMqT8zWtEzZD!juq`!NI zTfln9d~jEEOPh{~$hLx8GgNKz=PF!ikQa?BkuASpLECK$WY#S`KRC;X23b8_M?vBk zx09&r(mP$vjN00+FHf^nU)H9Agcb)aY7gLikSqr)HbUcC1O2aiw7-Kgmp?LCUw@Go zhDYbDW5{A-vrYD&vJd-*3xo?@@pCo5OTM=AfSj&Arc|d??+W12n8>;y*Kdl^gzNL- zI_4yFX6#dYQ#K#%Ro*_qsZqD;qWgdL4ctp4mu=IU9kFn((Dh~F_<61YYTw?BQ1u~> z;C~aSvZ?er$<}Mf6>fjWG5m!~4XK&@5l(=EyPQ_Q8$byz=uaziqn+KM%!qnYVgu{= z2iKS*Szf($7=QbK*c;C#)oU$FMwG|^WC}ra588|AIjr1m9OL3HlaOM9hGPcT<_8{^ zF#&)n?)LzKe4Q)9e}XGupKkz^cs>P5VFDU2dt_cFOyp1HBrkpTzbFuzJgR3dx_>zw zutmO`fKJ`BqrS{>{tUl?`heM+t^1{yspO@7kpo=Gq)k`949YISX-G@j$3nY z9IClH_aX%TZEm`D-t4weIqOVp#t(qqSNQ>1JTbT*zQpn2T-vq|f4Z6ZvCL5zZ^+kr zRFB>Mh)Q-FY!O9H+8aD6_^f5zw6cBih69bj%+0mMXIWkWPGcx*NNU?)4{+i;F`Ctz z(Wxdj7Ppc591~ST)T3=s%VTsKn*qt4?bi88&f;sQ*O>2Q)w*xL$A@6N1CDyqT{6gr zGofp*F(baV$4|d{@XLSw~h{kJjpd0&Wc z2+3gm+~X5SL}T=gmn%<0rsAo?Ntiq*DkW4lkSh%;M`p@L{*a`S+FFf4W|jM`HrNU} z7EBb|TsXGcOd#=p$cIg*K<&3KhsG=f{{q1&dp6 z<_jE=zO~KM0Z1u$I5Yr5BeeW&Cii#6X$a_xmZjhNhoYd5zdaZH?1z6zQ02^@KS_gT zk!UX5v0f#HRoH@W&wG0qrIRF1dt!_325+Dk#i97epo#ahlh+Z<#4?brw1Bxg>bql#+{{q)}Zc&f0I>%VFan!9~BNIGau72^`vZu`)Nh?8JnomSuLFO)(4 z;*uKA5up0R=NGo~#D2Fiu&q=h}v^0rhvV zXB4ZiRi#HqcCmT+r0jk{%PC@ENCtI3IMBz>VgT>;7M{HCfIvtJpY1U}5H(P0hN4>r zGi%!K4xhN^73rXz!A?Y9UnVc;QpROpTg?TIHOfKD%gW}=Te!kh!!Hrpz2m{AKiL`! z_ty8}9A<^a{rF9q3qseNVfQih0;Rj~SzPQLNp&@dH=14P=qSdux-RsE0h(;?xLt$a zvpg;9&7;CJKCyD2K`^P?_HIknfIdwM$-ViXd3}DY**Sk8C5qJjGf%MVo0L92$TZ(7 zC6X71H!AEc1t;fDKE0P*Q3%4$8;ZtL(|bVE;GZ@c85W*Klq9+m`MJT^kXH&(mZl4M z9_y%&UGaVq?+j!>kD@-`PD05u@p$}xck14RZ8P+BIABMn;0GG|fOoyWtI z=eI)YG}r`(F$@yV_@DHcegCGy0 z*tpR#V$Q(HnG(JyjEW!9F}t^)Mr2OG=sF_6GM>EAte?{9V%?#yskf9Aj=?ZXtH%N( zC|d*cRb=37c@*a(CcZcx=H_(sG?89WUj*jwmIv-*vSt zPM>CXL}B=Z`Q+t z;jq_kxuU#Ewi4N3R+jqbA} zI+YFEeFt9C@Nb+hFtF_C_fBkWKUt)7jZ0M~dOJ-llUct|kGi(L(2?=50-nF+Jm02x zpw2Ha`qR5oP76q14mQe{TeTb#3aA-Hn6yEoYyvn)vo-8i=1&o+Y>e|ycHn5O6%g-4 zfaNJ1-V+5fAInNej`v_g)<~=8g{8dW!@ax15t%eQRT?VqwvM&rWzc1z${hTxHMtU{ zh%Zi}#k;i|dXe+|1JRZCt_BrP;1f=AfVwlT( zuvossj}B}+4$%B>C>O|sTYx?oL!zG7x@kqnNn-5Xo%DykUUg%0$)Uu$?;UH+1B-MX zuY>VRT&o+-sB+rBwl*`65xrl>NU>kcBvE@%nvs$%h_SanIVUDB?QoUs5hxF>v9_ph=eUsS@{a)SKI+(QfkCo@mh zoc%A)`o;sH|86a>lps;~18%?T!HWkd8iPHhm^EO-PeILSX23=P25Q-lpY?#xSE3|l z$4cDXMso8CmF^!dZ*H&p`2|-R71wIcNDuODS@)z$lG&p8g6}mp=PH}D=PbW>loOxm z7wt{K_DspguAt@|_Lbe(QslgkL{=H~3bO4FZXu9*h43}dD8Mo_`+-z)hg*N9XnYo% zBwS=NJHWArvZ@75{K6?-MQjOJ$d$}cAMv9b8=n)EnjlWw4z?S!81(98?T5k<2&aF~ zBrx4f44caoTW_FvZ~112{Hw&ZJz8cIS> zQ5P>qAj(u_%1D$dHavDJiA4Uy83U3qp$V(q_f&n%Pjx8&M(b7!wDqu)RitKrtyv~WMx2l7C<=hUk-=#X3VGIj^f z_1J8wfoQu0^Nf-woI{T`D)924qd#D9OXE>Fg+pb?!#tQLmfv*Lp|B0VbDklsu#shM z#uMEj`Jp=nTmGO$C%Qrs32nW>YuhwBo9~#5zKgkwWz@=?WHapd2|5?ILs1Te0Ou(6 z$7W#ZpwJ1REVN6W;9ny}A{wfB z&y9M6rA_LCw(B+Gt#wdWc;G6*;F;o7OhuXD{PF7Ct#q7A>er{>uSKYLZewJ_k>iq; zEcD@gbi*@e2PIY}kJs+(#^!%3Gp%pbZybD!(z*QU8OLcZ_~_64>+*6JNV#4;Dli3p zD)48H)hz-PhUTIx{>tHK)d7tALt5Va?-XqlfcdH5AFuw((EfiI9ynsrSXt>_e*Y`& z4|k$7Hn60J;Y~2GF82nSqCWM!!b<7*S!V$4I0!@HBh0KAw176*`oND9jg?JSp7&9- zKm0F;DS8gx1OZLjGBnTowDJnm2n5+h_qb?z+IJo#P;4v;0Hu{K3(-;ejbVa|Mh4K5 zNyTffyf&3?fzt25dcyDJFlyD6gDwMPoRo>r~mAikP;&pN`(Xsh;?{Z1`&dS zIxdT>L{DkOjJo|IIDs9-ZHpN7NYk#|+p1w$)2mZ$9K_kGJ&gAs3cTfDioJKa$;e45 zMk=^W@)P^FLMZ_pT&pAJ{j+`w#K#H1((geC4CDXOsK0k5x&Bf}GN2gx4}pJp@Q)r4 z)BeMQe|T_N2>nM7{!jCOQA1&cfPgUK@_zvUm1Ww0ywv{{9{ju@jGPvJ|IS`V@bW)w O=x7>hRNi|S@jn2|)VwtS literal 0 HcmV?d00001 diff --git a/week-04/dev/my-dapp/assets/2-connect.png b/week-04/dev/my-dapp/assets/2-connect.png new file mode 100644 index 0000000000000000000000000000000000000000..fad7be7c2c571fff31364311e5a59efbc4a58cb9 GIT binary patch literal 30291 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%0Dq_liI5z-*!xZ;8hOVK9Ua;O|i=X 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 literal 0 HcmV?d00001 diff --git a/week-04/dev/my-dapp/assets/3-increment.png b/week-04/dev/my-dapp/assets/3-increment.png new file mode 100644 index 0000000000000000000000000000000000000000..c63ab3f9333621d2613fcbdb0020f85894645f66 GIT binary patch literal 33188 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 bno|PK3~p{}f0DNt;Gdk7vSg8j$&>#8t(?p= literal 0 HcmV?d00001 diff --git a/week-04/dev/my-dapp/assets/4-decrement.png b/week-04/dev/my-dapp/assets/4-decrement.png new file mode 100644 index 0000000000000000000000000000000000000000..8dff5367d54ccbaef9ea89e36807f3e638feef80 GIT binary patch literal 34088 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 BSONe5 literal 0 HcmV?d00001 diff --git a/week-04/dev/my-dapp/index.html b/week-04/dev/my-dapp/index.html new file mode 100644 index 0000000..06d42d0 --- /dev/null +++ b/week-04/dev/my-dapp/index.html @@ -0,0 +1,12 @@ + + + + + + Counter DApp + + +
+ + + diff --git a/week-04/dev/my-dapp/package.json b/week-04/dev/my-dapp/package.json new file mode 100644 index 0000000..aad41da --- /dev/null +++ b/week-04/dev/my-dapp/package.json @@ -0,0 +1,26 @@ +{ + "name": "counter-dapp", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@rainbow-me/rainbowkit": "^2.2.10", + "@tanstack/react-query": "^5.62.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.21.0", + "wagmi": "^2.14.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.2", + "vite": "^6.0.0" + } +} diff --git a/week-04/dev/my-dapp/src/App.css b/week-04/dev/my-dapp/src/App.css new file mode 100644 index 0000000..daa989b --- /dev/null +++ b/week-04/dev/my-dapp/src/App.css @@ -0,0 +1,153 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #1a1a2e; + color: #e0e0e0; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.container { + max-width: 480px; + width: 100%; + padding: 2rem; + text-align: center; +} + +h1 { + font-size: 2rem; + margin-bottom: 2rem; + color: #fff; +} + +.card { + background: #16213e; + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; +} + +.wallet-info { + font-size: 0.875rem; + color: #a0a0a0; + word-break: break-all; +} + +.wallet-info .address { + color: #64b5f6; + font-family: monospace; +} + +.wallet-info .chain { + color: #81c784; + margin-top: 0.25rem; +} + +.count { + font-size: 4rem; + font-weight: bold; + color: #fff; + margin: 0.5rem 0; +} + +.actions { + display: flex; + gap: 0.75rem; + justify-content: center; + flex-wrap: wrap; +} + +button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s, transform 0.1s; +} + +button:hover:not(:disabled) { + opacity: 0.85; +} + +button:active:not(:disabled) { + transform: scale(0.97); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-connect { + background: #4361ee; + color: #fff; + width: 100%; +} + +.btn-disconnect { + background: #e63946; + color: #fff; + width: 100%; + margin-top: 0.75rem; +} + +.btn-increment { + background: #2ec4b6; + color: #fff; +} + +.btn-decrement { + background: #ff6b6b; + color: #fff; +} + +.btn-reset { + background: #6c757d; + color: #fff; +} + +.tx-status { + margin-top: 0.75rem; + padding: 0.75rem; + border-radius: 8px; + font-size: 0.875rem; +} + +.tx-status.pending { + background: #fff3cd; + color: #856404; +} + +.tx-status.confirming { + background: #cce5ff; + color: #004085; +} + +.tx-status.success { + background: #d4edda; + color: #155724; +} + +.tx-status.error { + background: #f8d7da; + color: #721c24; +} + +.loading { + color: #a0a0a0; + font-style: italic; +} + +.error-text { + color: #e63946; + font-size: 0.875rem; +} diff --git a/week-04/dev/my-dapp/src/App.tsx b/week-04/dev/my-dapp/src/App.tsx new file mode 100644 index 0000000..5ad6d70 --- /dev/null +++ b/week-04/dev/my-dapp/src/App.tsx @@ -0,0 +1,30 @@ +import '@rainbow-me/rainbowkit/styles.css' + +import { RainbowKitProvider } from '@rainbow-me/rainbowkit' +import { WagmiProvider } from 'wagmi' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ConnectButton } from '@rainbow-me/rainbowkit' +import { config } from './wagmi' +import { CounterDisplay } from './components/CounterDisplay' +import { CounterActions } from './components/CounterActions' + +const queryClient = new QueryClient() + +function App() { + return ( + + + +
+

Counter DApp

+ + + +
+
+
+
+ ) +} + +export default App diff --git a/week-04/dev/my-dapp/src/components/CounterActions.tsx b/week-04/dev/my-dapp/src/components/CounterActions.tsx new file mode 100644 index 0000000..376ac7b --- /dev/null +++ b/week-04/dev/my-dapp/src/components/CounterActions.tsx @@ -0,0 +1,59 @@ +import { useAccount, useWriteContract } from 'wagmi' +import { useQueryClient } from '@tanstack/react-query' +import { counterAbi, counterAddress } from '../counter' +import { TransactionStatus } from './TransactionStatus' + +export function CounterActions() { + const { isConnected } = useAccount() + const queryClient = useQueryClient() + const { writeContract, data: hash, isPending, error, reset } = useWriteContract() + + const handleWrite = (functionName: 'increment' | 'decrement' | 'reset') => { + reset() + writeContract({ + address: counterAddress, + abi: counterAbi, + functionName, + }) + } + + const handleConfirmed = () => { + queryClient.invalidateQueries({ queryKey: ['readContract'] }) + } + + if (!isConnected) return null + + return ( +
+
+ + + +
+ +
+ ) +} diff --git a/week-04/dev/my-dapp/src/components/CounterDisplay.tsx b/week-04/dev/my-dapp/src/components/CounterDisplay.tsx new file mode 100644 index 0000000..ae35745 --- /dev/null +++ b/week-04/dev/my-dapp/src/components/CounterDisplay.tsx @@ -0,0 +1,42 @@ +import { useReadContract, useAccount } from 'wagmi' +import { counterAbi, counterAddress } from '../counter' + +export function CounterDisplay() { + const { isConnected } = useAccount() + const { data, isLoading, isError, error } = useReadContract({ + address: counterAddress, + abi: counterAbi, + functionName: 'getCount', + query: { enabled: isConnected }, + }) + + if (!isConnected) { + return ( +
+

지갑을 연결하세요

+
+ ) + } + + if (isLoading) { + return ( +
+

Loading...

+
+ ) + } + + if (isError) { + return ( +
+

Error: {error?.message ?? 'Failed to read count'}

+
+ ) + } + + return ( +
+
{data?.toString() ?? '—'}
+
+ ) +} diff --git a/week-04/dev/my-dapp/src/components/TransactionStatus.tsx b/week-04/dev/my-dapp/src/components/TransactionStatus.tsx new file mode 100644 index 0000000..09acb98 --- /dev/null +++ b/week-04/dev/my-dapp/src/components/TransactionStatus.tsx @@ -0,0 +1,60 @@ +import { useEffect } from 'react' +import { useWaitForTransactionReceipt } from 'wagmi' + +interface TransactionStatusProps { + hash: `0x${string}` | undefined + isPending: boolean + error: Error | null + onConfirmed?: () => void +} + +export function TransactionStatus({ hash, isPending, error, onConfirmed }: TransactionStatusProps) { + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ + hash, + query: { + enabled: !!hash, + }, + }) + + useEffect(() => { + if (isSuccess && onConfirmed) { + onConfirmed() + } + }, [isSuccess, onConfirmed]) + + if (!isPending && !hash && !error) return null + + if (error) { + return ( +
+ Error: {error.message.split('\n')[0]} +
+ ) + } + + if (isPending) { + return ( +
+ 지갑에서 트랜잭션을 확인해주세요... +
+ ) + } + + if (isConfirming) { + return ( +
+ 트랜잭션 확인 중... +
+ ) + } + + if (isSuccess) { + return ( +
+ 트랜잭션 성공! +
+ ) + } + + return null +} diff --git a/week-04/dev/my-dapp/src/counter.ts b/week-04/dev/my-dapp/src/counter.ts new file mode 100644 index 0000000..8c56d1f --- /dev/null +++ b/week-04/dev/my-dapp/src/counter.ts @@ -0,0 +1,39 @@ +export const counterAbi = [ + { + type: 'function', + name: 'count', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCount', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'increment', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decrement', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'reset', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, +] as const + +export const counterAddress = import.meta.env.VITE_COUNTER_ADDRESS as `0x${string}` diff --git a/week-04/dev/my-dapp/src/main.tsx b/week-04/dev/my-dapp/src/main.tsx new file mode 100644 index 0000000..43528b2 --- /dev/null +++ b/week-04/dev/my-dapp/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' +import './App.css' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/week-04/dev/my-dapp/src/vite-env.d.ts b/week-04/dev/my-dapp/src/vite-env.d.ts new file mode 100644 index 0000000..c6c1093 --- /dev/null +++ b/week-04/dev/my-dapp/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_COUNTER_ADDRESS: string + readonly VITE_WALLETCONNECT_PROJECT_ID: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/week-04/dev/my-dapp/src/wagmi.ts b/week-04/dev/my-dapp/src/wagmi.ts new file mode 100644 index 0000000..cd783b1 --- /dev/null +++ b/week-04/dev/my-dapp/src/wagmi.ts @@ -0,0 +1,13 @@ +import { getDefaultConfig } from '@rainbow-me/rainbowkit' +import { http } from 'wagmi' +import { foundry, sepolia } from 'wagmi/chains' + +export const config = getDefaultConfig({ + appName: 'Counter DApp', + projectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID, + chains: [foundry, sepolia], + transports: { + [foundry.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/week-04/dev/my-dapp/tsconfig.json b/week-04/dev/my-dapp/tsconfig.json new file mode 100644 index 0000000..02bc281 --- /dev/null +++ b/week-04/dev/my-dapp/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/week-04/dev/my-dapp/tsconfig.node.json b/week-04/dev/my-dapp/tsconfig.node.json new file mode 100644 index 0000000..be1e141 --- /dev/null +++ b/week-04/dev/my-dapp/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "composite": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": false, + "declaration": true, + "declarationMap": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/week-04/dev/my-dapp/vite.config.ts b/week-04/dev/my-dapp/vite.config.ts new file mode 100644 index 0000000..9ffcc67 --- /dev/null +++ b/week-04/dev/my-dapp/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], +}) diff --git a/week-04/dev/script/Counter.s.sol b/week-04/dev/script/Counter.s.sol new file mode 100644 index 0000000..8bb8fd4 --- /dev/null +++ b/week-04/dev/script/Counter.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Script.sol"; +import "../src/Counter.sol"; + +contract CounterScript is Script { + function run() external { + vm.broadcast(); + Counter counter = new Counter(); + console.log("Counter deployed at:", address(counter)); + } +} diff --git a/week-04/dev/src/Counter.sol b/week-04/dev/src/Counter.sol new file mode 100644 index 0000000..f7cde30 --- /dev/null +++ b/week-04/dev/src/Counter.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +contract Counter { + uint256 public count; + + function getCount() public view returns (uint256) { + return count; + } + + function increment() public { + count += 1; + } + + function decrement() public { + require(count > 0, "Count cannot go below zero"); + count -= 1; + } + + function reset() public { + count = 0; + } +} diff --git a/week-04/quiz/quiz-04-solution.md b/week-04/quiz/quiz-04-solution.md new file mode 100644 index 0000000..55363f7 --- /dev/null +++ b/week-04/quiz/quiz-04-solution.md @@ -0,0 +1,392 @@ +# Week 4 Quiz: Network/Block + wagmi + +> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요. +> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다. + +--- + +## 문제 1: 블록 헤더 필드 (객관식) + +다음 상황을 고려하세요: + +``` +블록 100의 해시: 0xabc123... +블록 101의 해시: 0xdef456... +``` + +블록 101의 `parentHash` 필드에는 어떤 값이 저장되어 있나요? 그리고 **왜** 이런 방식으로 연결하나요? + +**보기:** +A) 0xdef456... - 자기 자신의 해시를 저장하여 무결성을 보장한다 +B) 0xabc123... - 이전 블록의 해시를 저장하여 체인 연결과 불변성을 보장한다 +C) 블록 번호 100 - 숫자로 순서를 추적한다 +D) 빈 값 - 헤더에는 해시가 저장되지 않는다 + +**답변:** + +B, 다음 블록은 부모 블록의 해시를 가지고 있어야한다. 말그대로 체인이기때문이다. A 값이 바뀌면 B 값도 바뀌어야한다. + +A, 역설이다. 해시는 블록이 생성된 이후에 계산된다. 생성되기도 전에 해시를 알 수 없다. +C, 해시는 완전 무결한 값이기에 블록 번호로 하면 안된다. 뭐가 바뀌었는지 모른다. +D, 헤더에 아무것도 안하면 블록체인이 아니다. + +--- + +## 문제 2: MPT 목적 (객관식) + +이더리움에서 Merkle Patricia Trie(MPT)를 사용하는 **가장 중요한 이유**는 무엇인가요? + +**보기:** +A) 데이터를 암호화하여 외부에서 읽을 수 없게 한다 +B) 트랜잭션 처리 속도를 10배 이상 높인다 +C) 전체 데이터 없이도 특정 데이터의 존재와 정확성을 효율적으로 증명한다 +D) 블록 크기를 줄여서 저장 공간을 절약한다 + +**답변:** + +C, 머클 증명을 통해 데이터 처리에 한계가 있는 디바이스가 쉽게 데이터 확인을 할 수 있다. + +--- + +## 문제 3: 체인 연결과 보안 (객관식) + +공격자가 블록 50의 트랜잭션을 수정하려고 합니다. 현재 체인의 최신 블록은 100입니다. 이 공격이 **왜** 어려운가요? + +**보기:** +A) 블록 50은 너무 오래되어서 시스템에서 접근할 수 없다 +B) 블록 50을 수정하면 해시가 바뀌고, 블록 51부터 100까지 모든 블록의 parentHash가 불일치하게 된다 +C) 블록 50은 이미 암호화되어 있어서 복호화 키가 필요하다 +D) 네트워크 관리자만 과거 블록을 수정할 수 있다 + +**답변:** + +블록 100 부터 50까지 전부 바꿔야하므로 매우 어려운 작업이다. 근데 101, 102 도 계속 생성되니 바꾸기가 비현실적인것. + +--- + +## 문제 4: MPT 진화 과정 (단답형) + +MPT(Merkle Patricia Trie)는 세 가지 자료구조의 장점을 결합한 것입니다: +1. **Trie** -> 2. **Patricia Trie** -> 3. **Merkle Patricia Trie** + +**왜** 각 단계의 발전이 필요했나요? 각 단계가 해결하는 문제를 간단히 설명하세요. + +**답변:** + +1. Trie가 해결하는 문제: +- 사전구조와 비슷하므로 데이터 길이만 길어질뿐 찾는 속도는 빠르다. 하지만 데이터가 길어지면 메모리 낭비가 심하다. +2. Patricia Trie가 해결하는 문제 (Trie의 한계): +- 자식 노드가 하나뿐인 노드를 합쳐버렸다. 저장공간을 줄였다. 하지만 수정이 되었는지 증명하는 방법이 부족했다. +3. Merkle Patricia Trie가 해결하는 문제 (Patricia Trie의 한계): +- 머클 증명을 통해 트리 전체를 대표하는 머클 해시를 만들어 굳이 전체 탐색을 안해도 증명할 수 있게 되었다. + + + +--- + +## 문제 5: Eclipse Attack 방어 (단답형) + +Eclipse Attack은 공격자가 피해자 노드의 **모든 피어 연결**을 자신이 통제하는 노드로 바꾸는 공격입니다. + +1) 이 공격이 성공하면 피해자에게 **어떤 피해**가 발생할 수 있나요? +2) 개인 노드 운영자가 이 공격을 **방어**하기 위해 할 수 있는 행동은 무엇인가요? + +**답변:** +1) 가능한 피해 (2가지 이상): +- 이중지불 +- 채굴 낭비 +- 담합 + +2) 방어 방법 (2가지 이상): +- 신뢰할만한 노드 추가 +- 피어 교체 +- ip 다양성 확보 + + +--- + +## 문제 6: 노드 종류 선택 (단답형) + +친구가 이더리움 개발을 시작하려고 합니다. 다음 세 가지 상황에서 각각 어떤 노드 타입(Full, Light, Archive)을 추천하시겠습니까? **왜** 그 노드를 추천하는지도 설명하세요. + +1) 모바일 지갑 앱 개발 +2) 블록체인 데이터 분석 서비스 개발 +3) 일반적인 dApp 백엔드 개발 + +**답변:** +1) 모바일 지갑 앱: + 추천 노드: Light Node + 이유: 모바일 환경은 제한된 저장공간을 가지고있다. Light Node는 모든 블록 데이터를 받지 않고 블록 헤더 구조만 다운로드하여 용량을 획기적으로 줄이면서도, 머클 증명을 통해 트랜잭션의 유효성을 검증할 수 있기 때문에 모바일 환경에 적합하다. + +2) 블록체인 데이터 분석: + 추천 노드: Archive Node + 이유: Archive Node는 블록체인의 모든 기록과 과거의 상태(State)를 빠짐없이 가지고 있어 데이터 조회 요구사항을 충족할 수 있다. + +3) dApp 백엔드: + 추천 노드: Full Node + 이유: 일반적인 dApp 운영에서는 새 트랜잭션을 검증 및 전파하고, 최신 블록체인 상태 데이터를 읽어오는 것이 가장 중요하다. Full Node는 이러한 독자적인 데이터 검증 및 상태 관리 등을 자체적으로 처리할 수 있으면서도, Archive Node에 비해 스토리지 용량 유지 및 운영 비용 면에서 훨씬 합리적이다. + +--- + +## 문제 7: useAccount Hook (빈칸 채우기) + +다음 코드의 빈칸을 채워서 지갑 연결 상태를 표시하는 컴포넌트를 완성하세요: + +```typescript +import { _________________ } from 'wagmi'; + +function WalletStatus() { + // TODO: useAccount hook에서 필요한 값들을 가져오세요 + const { _________________, _________________ } = useAccount(); + + if (!isConnected) { + return
지갑이 연결되지 않았습니다
; + } + + return ( +
+

연결된 주소: {address}

+
+ ); +} +``` + +**답변:** +```typescript +import { useAccount } from 'wagmi'; + +function WalletStatus() { + // TODO: useAccount hook에서 필요한 값들을 가져오세요 + const { isConnected, address } = useAccount(); + + if (!isConnected) { + return
지갑이 연결되지 않았습니다
; + } + + return ( +
+

연결된 주소: {address}

+
+ ); +} +``` + +**왜 이렇게 작성했나요:** + + +useAccount 훅은 현재 지갑 프로바이더에 연결이 되어있는지 여부를 판단하는 isConnected와 연결된 지갑의 주소를 반환하는 address를 제공한다. + +**3버전 부터는 useAccount -> useConnection 으로 변경되었습니다~ 참고하시길 바랍니다.** +--- + +## 문제 8: useReadContract Hook (빈칸 채우기) + +다음 코드의 빈칸을 채워서 컨트랙트의 `getCount` 함수 결과를 화면에 표시하세요: + +```typescript +import { useReadContract } from 'wagmi'; + +const counterABI = [ + { + name: 'getCount', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'count', type: 'uint256' }], + }, +] as const; + +function CountDisplay() { + const { data, isLoading, error } = useReadContract({ + // TODO: 필요한 설정을 채우세요 + address: '0x1234...5678', + _________________, + _________________, + }); + + if (isLoading) return
로딩 중...
; + if (error) return
에러 발생
; + + return
현재 카운트: {_________________}
; +} +``` + +**답변:** +```typescript +// 완성된 코드를 여기에 작성하세요 +function CountDisplay() { + const { data, isLoading, error } = useReadContract({ + // TODO: 필요한 설정을 채우세요 + address: '0x1234...5678', + abi: counterABI, + functionName: 'getCount', + }); + + if (isLoading) return
로딩 중...
; + if (error) return
에러 발생
; + + return
현재 카운트: {data}
; +} +``` + +**왜 이렇게 작성했나요:** + +useReadContract 함수는 컨트랙트 주소, abi, 함수 이름이 필요합니다. data 는 함수 로딩이 끝난후에만 확인가능합니다. 에러를 안받으려면 위에서 조건처리를 해야합니다. + +--- + +## 문제 9: useWriteContract 버그 (취약점 찾기) + +다음 코드에서 **문제점**을 찾고 수정하세요: + +```typescript +// BAD CODE - 문제점 찾기 +import { useWriteContract } from 'wagmi'; + +function IncrementButton() { + const { writeContract, isPending } = useWriteContract(); + + const handleClick = () => { + // 문제가 있는 코드 + writeContract({ + address: '0x1234...5678', + functionName: 'increment', + // abi가 없음! + }); + }; + + return ( + + ); +} +``` + +**1) 발견한 문제점:** + +abi 가 없습니다. + +**2) 왜 이것이 문제인가:** + +abi 를 통해 타입검증을 해야하는데 못해서 에러를 뿜습니다. + +**3) 올바른 수정 방법:** +```typescript +// GOOD CODE - 수정된 버전을 작성하세요 +import { useWriteContract } from 'wagmi'; + +function IncrementButton() { + const { writeContract, isPending } = useWriteContract(); + + const handleClick = () => { + // 문제가 있는 코드 + writeContract({ + address: '0x1234...5678', + functionName: 'increment', + abi: counterABI, + }); + }; + + return ( + + ); +} +``` + +**3버전 부터는 약간 개발자 편의성이 증대했습니다. 이제 못생기게 writeTransaction, isLoading .. 하지말고 하나의 객체로 받아오면 됩니다.** + +--- + +## 문제 10: 블록 연결 구조 (다이어그램 해석) + +다음 다이어그램은 블록체인의 연결 구조를 보여줍니다: + +```mermaid +graph LR + subgraph B0["제네시스 블록"] + H0["hash: 0xabc..."] + end + subgraph B1["블록 1"] + PH1["parent: 0xabc..."] + H1["hash: 0xdef..."] + end + subgraph B2["블록 2"] + PH2["parent: 0xdef..."] + H2["hash: 0x123..."] + end + subgraph B3["블록 3"] + PH3["parent: ???"] + H3["hash: 0x789..."] + end + + B0 --> B1 --> B2 --> B3 +``` + +**질문:** + +1) 블록 3의 `parent: ???` 에 들어갈 값은 무엇인가요? +- 0x123... + +2) 만약 블록 1의 내용이 수정되면, 블록 2와 블록 3에 **어떤 영향**이 있나요? 왜 그런가요? +- 해시값이 수정된다. 부모 해시의 값이 해시 생성에 포함되기 때문이다. + +3) 제네시스 블록(블록 0)의 parentHash는 어떤 특별한 값을 가지나요? 왜 그런가요? +- 0x000... 최초 블록이기에 0 이다. 비트코인은 여기에 유명한 글을 쓰기도 했다. + +--- + +## 문제 11: MPT 트리 구조 (다이어그램 해석) + +다음 다이어그램은 MPT의 노드 구조를 보여줍니다: + +```mermaid +graph TD + ROOT["Root Hash: 0xfff..."] --> EXT1["Extension Node
path: 0a"] + ROOT --> EXT2["Extension Node
path: 0b"] + + EXT1 --> BRANCH["Branch Node
(16개 슬롯)"] + BRANCH --> LEAF1["Leaf: 계정 A
주소: 0a1234..."] + BRANCH --> LEAF2["Leaf: 계정 B
주소: 0a5678..."] + + EXT2 --> LEAF3["Leaf: 계정 C
주소: 0b9999..."] +``` + +**질문:** + +1) 계정 A와 계정 B가 같은 Branch Node 아래에 있는 이유는 무엇인가요? (주소 패턴을 힌트로 사용하세요) +- 공통 접두어로 시작하기 때문에 (0a) + +2) Extension Node가 하는 역할은 무엇인가요? 없다면 어떤 문제가 생기나요? +- 자식이 하나뿐인 연속된 경로를 하나로 합침. 글자마다 노드를 생성하기 떄문에 용량이 커진다. + +3) Root Hash만 알면 어떻게 특정 계정의 데이터 존재를 **증명**할 수 있나요? (Light Client 관점에서) +- 머클 증명을 사용하기 떄문에 데이터 변경시 머클 해시가 변경된다. 라이트 클라이언트는 전체 노드를 다 몰라도 되니까 머클 해시만 알면 된다. + +--- + +## 제출 전 체크리스트 + +- [ ] 모든 문제에 답변을 작성했는가? +- [ ] 객관식 문제: 정답 선택 **이유**를 설명했는가? +- [ ] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? +- [ ] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? +- [ ] 다이어그램 문제: 각 질문에 논리적으로 답변했는가? diff --git a/week-04/quiz/quiz-04.md b/week-04/quiz/quiz-04.md index 91dfa07..16f4f3c 100644 --- a/week-04/quiz/quiz-04.md +++ b/week-04/quiz/quiz-04.md @@ -116,20 +116,6 @@ Eclipse Attack은 공격자가 피해자 노드의 **모든 피어 연결**을 3) 일반적인 dApp 백엔드 개발 **답변:** - - --- From b7d8fd584ed441e41105aad8368ca179659a874e Mon Sep 17 00:00:00 2001 From: jaesimin Date: Thu, 12 Mar 2026 12:15:43 +0900 Subject: [PATCH 2/2] feat(week-05): complete dapp assignment --- week-05/dev/my-dapp/.env.example | 6 + week-05/dev/my-dapp/index.html | 12 + week-05/dev/my-dapp/package.json | 26 ++ week-05/dev/my-dapp/src/App.css | 153 +++++++ week-05/dev/my-dapp/src/App.tsx | 30 ++ .../my-dapp/src/components/CounterActions.tsx | 59 +++ .../my-dapp/src/components/CounterDisplay.tsx | 42 ++ .../src/components/CustomWalletButton.tsx | 39 ++ .../my-dapp/src/components/SenEthDisplay.tsx | 108 +++++ .../src/components/TransactionStatus.tsx | 60 +++ week-05/dev/my-dapp/src/counter.ts | 39 ++ week-05/dev/my-dapp/src/main.tsx | 10 + week-05/dev/my-dapp/src/vite-env.d.ts | 10 + week-05/dev/my-dapp/src/wagmi.ts | 13 + week-05/dev/my-dapp/tsconfig.json | 23 + week-05/dev/my-dapp/tsconfig.node.json | 23 + week-05/dev/my-dapp/vite.config.ts | 6 + week-05/dev/script/Counter.s.sol | 27 ++ week-05/dev/src/Counter.sol | 23 + week-05/theory/quiz-05-solution.md | 425 ++++++++++++++++++ 20 files changed, 1134 insertions(+) create mode 100644 week-05/dev/my-dapp/.env.example create mode 100644 week-05/dev/my-dapp/index.html create mode 100644 week-05/dev/my-dapp/package.json create mode 100644 week-05/dev/my-dapp/src/App.css create mode 100644 week-05/dev/my-dapp/src/App.tsx create mode 100644 week-05/dev/my-dapp/src/components/CounterActions.tsx create mode 100644 week-05/dev/my-dapp/src/components/CounterDisplay.tsx create mode 100644 week-05/dev/my-dapp/src/components/CustomWalletButton.tsx create mode 100644 week-05/dev/my-dapp/src/components/SenEthDisplay.tsx create mode 100644 week-05/dev/my-dapp/src/components/TransactionStatus.tsx create mode 100644 week-05/dev/my-dapp/src/counter.ts create mode 100644 week-05/dev/my-dapp/src/main.tsx create mode 100644 week-05/dev/my-dapp/src/vite-env.d.ts create mode 100644 week-05/dev/my-dapp/src/wagmi.ts create mode 100644 week-05/dev/my-dapp/tsconfig.json create mode 100644 week-05/dev/my-dapp/tsconfig.node.json create mode 100644 week-05/dev/my-dapp/vite.config.ts create mode 100644 week-05/dev/script/Counter.s.sol create mode 100644 week-05/dev/src/Counter.sol create mode 100644 week-05/theory/quiz-05-solution.md diff --git a/week-05/dev/my-dapp/.env.example b/week-05/dev/my-dapp/.env.example new file mode 100644 index 0000000..60be164 --- /dev/null +++ b/week-05/dev/my-dapp/.env.example @@ -0,0 +1,6 @@ +# Counter 컨트랙트 배포 주소 +# Anvil에서 배포 후 주소를 .env.local에 설정하세요 +VITE_COUNTER_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + +# WalletConnect Project ID (https://cloud.walletconnect.com 에서 발급) +VITE_WALLETCONNECT_PROJECT_ID=YOUR_PROJECT_ID diff --git a/week-05/dev/my-dapp/index.html b/week-05/dev/my-dapp/index.html new file mode 100644 index 0000000..06d42d0 --- /dev/null +++ b/week-05/dev/my-dapp/index.html @@ -0,0 +1,12 @@ + + + + + + Counter DApp + + +
+ + + diff --git a/week-05/dev/my-dapp/package.json b/week-05/dev/my-dapp/package.json new file mode 100644 index 0000000..aad41da --- /dev/null +++ b/week-05/dev/my-dapp/package.json @@ -0,0 +1,26 @@ +{ + "name": "counter-dapp", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@rainbow-me/rainbowkit": "^2.2.10", + "@tanstack/react-query": "^5.62.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "^2.21.0", + "wagmi": "^2.14.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.2", + "vite": "^6.0.0" + } +} diff --git a/week-05/dev/my-dapp/src/App.css b/week-05/dev/my-dapp/src/App.css new file mode 100644 index 0000000..daa989b --- /dev/null +++ b/week-05/dev/my-dapp/src/App.css @@ -0,0 +1,153 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #1a1a2e; + color: #e0e0e0; + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.container { + max-width: 480px; + width: 100%; + padding: 2rem; + text-align: center; +} + +h1 { + font-size: 2rem; + margin-bottom: 2rem; + color: #fff; +} + +.card { + background: #16213e; + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; +} + +.wallet-info { + font-size: 0.875rem; + color: #a0a0a0; + word-break: break-all; +} + +.wallet-info .address { + color: #64b5f6; + font-family: monospace; +} + +.wallet-info .chain { + color: #81c784; + margin-top: 0.25rem; +} + +.count { + font-size: 4rem; + font-weight: bold; + color: #fff; + margin: 0.5rem 0; +} + +.actions { + display: flex; + gap: 0.75rem; + justify-content: center; + flex-wrap: wrap; +} + +button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s, transform 0.1s; +} + +button:hover:not(:disabled) { + opacity: 0.85; +} + +button:active:not(:disabled) { + transform: scale(0.97); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-connect { + background: #4361ee; + color: #fff; + width: 100%; +} + +.btn-disconnect { + background: #e63946; + color: #fff; + width: 100%; + margin-top: 0.75rem; +} + +.btn-increment { + background: #2ec4b6; + color: #fff; +} + +.btn-decrement { + background: #ff6b6b; + color: #fff; +} + +.btn-reset { + background: #6c757d; + color: #fff; +} + +.tx-status { + margin-top: 0.75rem; + padding: 0.75rem; + border-radius: 8px; + font-size: 0.875rem; +} + +.tx-status.pending { + background: #fff3cd; + color: #856404; +} + +.tx-status.confirming { + background: #cce5ff; + color: #004085; +} + +.tx-status.success { + background: #d4edda; + color: #155724; +} + +.tx-status.error { + background: #f8d7da; + color: #721c24; +} + +.loading { + color: #a0a0a0; + font-style: italic; +} + +.error-text { + color: #e63946; + font-size: 0.875rem; +} diff --git a/week-05/dev/my-dapp/src/App.tsx b/week-05/dev/my-dapp/src/App.tsx new file mode 100644 index 0000000..7c5754a --- /dev/null +++ b/week-05/dev/my-dapp/src/App.tsx @@ -0,0 +1,30 @@ +import '@rainbow-me/rainbowkit/styles.css' + +import { RainbowKitProvider } from '@rainbow-me/rainbowkit' +import { WagmiProvider } from 'wagmi' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { config } from './wagmi' +import { CounterDisplay } from './components/CounterDisplay' +import { CounterActions } from './components/CounterActions' +import CustomWalletButton from './components/CustomWalletButton' +import SenEthDisplay from './components/SenEthDisplay' + +const queryClient = new QueryClient() + +function App() { + return ( + + + +
+

Counter DApp

+ + +
+
+
+
+ ) +} + +export default App diff --git a/week-05/dev/my-dapp/src/components/CounterActions.tsx b/week-05/dev/my-dapp/src/components/CounterActions.tsx new file mode 100644 index 0000000..376ac7b --- /dev/null +++ b/week-05/dev/my-dapp/src/components/CounterActions.tsx @@ -0,0 +1,59 @@ +import { useAccount, useWriteContract } from 'wagmi' +import { useQueryClient } from '@tanstack/react-query' +import { counterAbi, counterAddress } from '../counter' +import { TransactionStatus } from './TransactionStatus' + +export function CounterActions() { + const { isConnected } = useAccount() + const queryClient = useQueryClient() + const { writeContract, data: hash, isPending, error, reset } = useWriteContract() + + const handleWrite = (functionName: 'increment' | 'decrement' | 'reset') => { + reset() + writeContract({ + address: counterAddress, + abi: counterAbi, + functionName, + }) + } + + const handleConfirmed = () => { + queryClient.invalidateQueries({ queryKey: ['readContract'] }) + } + + if (!isConnected) return null + + return ( +
+
+ + + +
+ +
+ ) +} diff --git a/week-05/dev/my-dapp/src/components/CounterDisplay.tsx b/week-05/dev/my-dapp/src/components/CounterDisplay.tsx new file mode 100644 index 0000000..ae35745 --- /dev/null +++ b/week-05/dev/my-dapp/src/components/CounterDisplay.tsx @@ -0,0 +1,42 @@ +import { useReadContract, useAccount } from 'wagmi' +import { counterAbi, counterAddress } from '../counter' + +export function CounterDisplay() { + const { isConnected } = useAccount() + const { data, isLoading, isError, error } = useReadContract({ + address: counterAddress, + abi: counterAbi, + functionName: 'getCount', + query: { enabled: isConnected }, + }) + + if (!isConnected) { + return ( +
+

지갑을 연결하세요

+
+ ) + } + + if (isLoading) { + return ( +
+

Loading...

+
+ ) + } + + if (isError) { + return ( +
+

Error: {error?.message ?? 'Failed to read count'}

+
+ ) + } + + return ( +
+
{data?.toString() ?? '—'}
+
+ ) +} diff --git a/week-05/dev/my-dapp/src/components/CustomWalletButton.tsx b/week-05/dev/my-dapp/src/components/CustomWalletButton.tsx new file mode 100644 index 0000000..6dc31e3 --- /dev/null +++ b/week-05/dev/my-dapp/src/components/CustomWalletButton.tsx @@ -0,0 +1,39 @@ +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { useAccount, useDisconnect } from "wagmi"; + +export default function CustomWalletButton() { + const { isConnected, address, chainId } = useAccount(); + const { disconnect } = useDisconnect(); + return( + + {({ account, chain, openAccountModal, openChainModal, openConnectModal, mounted }) => { + // 서버 사이드 렌더링과 클라이언트 렌더링을 맞추기 위해 mounted 체크 필요 + if (!mounted) return null; + + return ( +
+ {isConnected ? ( +
+ {/* 네트워크/체인 변경 모달 띄우기 */} + + {/* 계정 정보 모달 띄우기 */} + + +
+ ) : ( + + )} +
+ ); + }} +
+); +} diff --git a/week-05/dev/my-dapp/src/components/SenEthDisplay.tsx b/week-05/dev/my-dapp/src/components/SenEthDisplay.tsx new file mode 100644 index 0000000..7199587 --- /dev/null +++ b/week-05/dev/my-dapp/src/components/SenEthDisplay.tsx @@ -0,0 +1,108 @@ +import React, { useState } from 'react'; +import { useAccount, useBalance, useSendTransaction, useWaitForTransactionReceipt } from 'wagmi'; +import { parseEther } from 'viem'; + +export default function SenEthDisplay() { + const [toAddress, setToAddress] = useState(''); + const [amount, setAmount] = useState(''); + + // 내 지갑 정보와 잔액 가져오기 + const { address } = useAccount(); + const { data: balanceData } = useBalance({ address }); + + // ETH 전송을 위한 훅 + const { + data: hash, + isPending, + error: sendError, + sendTransaction + } = useSendTransaction(); + + // 트랜잭션이 블록에 포함될 때까지 기다리기 위한 훅 + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!toAddress || !amount) return; + + // 이더를 Wei 단위로 변환 후 전송 (ex. 0.1 ETH -> 100000000000000000 Wei) + sendTransaction({ + to: toAddress as `0x${string}`, + value: parseEther(amount), + }); + }; + + return ( +
+

이더(ETH) 전송하기

+ + {balanceData && ( +
+ 내 지갑 잔액: {Number(balanceData.formatted).toFixed(4)} {balanceData.symbol} +
+ )} + +
+
+ + setToAddress(e.target.value)} + style={{ width: '100%', padding: '10px', boxSizing: 'border-box', borderRadius: '4px', border: '1px solid #ccc' }} + required + disabled={isPending || isConfirming} + /> +
+
+ + setAmount(e.target.value)} + style={{ width: '100%', padding: '10px', boxSizing: 'border-box', borderRadius: '4px', border: '1px solid #ccc' }} + required + disabled={isPending || isConfirming} + /> +
+ +
+ +
+ {hash && ( +
+ 트랜잭션 해시: {hash.slice(0, 10)}...{hash.slice(-8)} +
+ )} + {isConfirming &&
⏳ 트랜잭션이 블록에 포함되기를 기다리는 중입니다...
} + {isConfirmed &&
✅ 전송 완료! 트랜잭션이 성공적으로 처리되었습니다.
} + {sendError && ( +
+ ❌ 전송 실패: {((sendError as any).shortMessage || sendError.message)} +
+ )} +
+
+ ); +} diff --git a/week-05/dev/my-dapp/src/components/TransactionStatus.tsx b/week-05/dev/my-dapp/src/components/TransactionStatus.tsx new file mode 100644 index 0000000..09acb98 --- /dev/null +++ b/week-05/dev/my-dapp/src/components/TransactionStatus.tsx @@ -0,0 +1,60 @@ +import { useEffect } from 'react' +import { useWaitForTransactionReceipt } from 'wagmi' + +interface TransactionStatusProps { + hash: `0x${string}` | undefined + isPending: boolean + error: Error | null + onConfirmed?: () => void +} + +export function TransactionStatus({ hash, isPending, error, onConfirmed }: TransactionStatusProps) { + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ + hash, + query: { + enabled: !!hash, + }, + }) + + useEffect(() => { + if (isSuccess && onConfirmed) { + onConfirmed() + } + }, [isSuccess, onConfirmed]) + + if (!isPending && !hash && !error) return null + + if (error) { + return ( +
+ Error: {error.message.split('\n')[0]} +
+ ) + } + + if (isPending) { + return ( +
+ 지갑에서 트랜잭션을 확인해주세요... +
+ ) + } + + if (isConfirming) { + return ( +
+ 트랜잭션 확인 중... +
+ ) + } + + if (isSuccess) { + return ( +
+ 트랜잭션 성공! +
+ ) + } + + return null +} diff --git a/week-05/dev/my-dapp/src/counter.ts b/week-05/dev/my-dapp/src/counter.ts new file mode 100644 index 0000000..8c56d1f --- /dev/null +++ b/week-05/dev/my-dapp/src/counter.ts @@ -0,0 +1,39 @@ +export const counterAbi = [ + { + type: 'function', + name: 'count', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCount', + inputs: [], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'increment', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decrement', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'reset', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, +] as const + +export const counterAddress = import.meta.env.VITE_COUNTER_ADDRESS as `0x${string}` diff --git a/week-05/dev/my-dapp/src/main.tsx b/week-05/dev/my-dapp/src/main.tsx new file mode 100644 index 0000000..43528b2 --- /dev/null +++ b/week-05/dev/my-dapp/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' +import './App.css' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/week-05/dev/my-dapp/src/vite-env.d.ts b/week-05/dev/my-dapp/src/vite-env.d.ts new file mode 100644 index 0000000..c6c1093 --- /dev/null +++ b/week-05/dev/my-dapp/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_COUNTER_ADDRESS: string + readonly VITE_WALLETCONNECT_PROJECT_ID: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/week-05/dev/my-dapp/src/wagmi.ts b/week-05/dev/my-dapp/src/wagmi.ts new file mode 100644 index 0000000..cd783b1 --- /dev/null +++ b/week-05/dev/my-dapp/src/wagmi.ts @@ -0,0 +1,13 @@ +import { getDefaultConfig } from '@rainbow-me/rainbowkit' +import { http } from 'wagmi' +import { foundry, sepolia } from 'wagmi/chains' + +export const config = getDefaultConfig({ + appName: 'Counter DApp', + projectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID, + chains: [foundry, sepolia], + transports: { + [foundry.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/week-05/dev/my-dapp/tsconfig.json b/week-05/dev/my-dapp/tsconfig.json new file mode 100644 index 0000000..02bc281 --- /dev/null +++ b/week-05/dev/my-dapp/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/week-05/dev/my-dapp/tsconfig.node.json b/week-05/dev/my-dapp/tsconfig.node.json new file mode 100644 index 0000000..be1e141 --- /dev/null +++ b/week-05/dev/my-dapp/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "composite": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": false, + "declaration": true, + "declarationMap": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/week-05/dev/my-dapp/vite.config.ts b/week-05/dev/my-dapp/vite.config.ts new file mode 100644 index 0000000..9ffcc67 --- /dev/null +++ b/week-05/dev/my-dapp/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], +}) diff --git a/week-05/dev/script/Counter.s.sol b/week-05/dev/script/Counter.s.sol new file mode 100644 index 0000000..af4e481 --- /dev/null +++ b/week-05/dev/script/Counter.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Script.sol"; +import "../src/Counter.sol"; + +contract CounterScript is Script { + function run() external { + vm.startBroadcast(); + + // 1. 카운터 컨트랙트 배포 + Counter counter = new Counter(); + console.log("Counter deployed at:", address(counter)); + + // 2. 특정 주소로 테스트 이더(ETH) 전송하기 + address payable toUser = payable(0xF8D09e078D3552Ba1a5ae9876D3b24AA10B1EFAD); + uint256 amountToFund = 10 ether; + + // 이더 전송 후 성공 여부 확인 + (bool success, ) = toUser.call{ value: amountToFund }(""); + require(success, "ETH Transfer failed in deployment script"); + + console.log("Sent", amountToFund / 1e18, "ETH to:", toUser); + + vm.stopBroadcast(); + } +} diff --git a/week-05/dev/src/Counter.sol b/week-05/dev/src/Counter.sol new file mode 100644 index 0000000..f7cde30 --- /dev/null +++ b/week-05/dev/src/Counter.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +contract Counter { + uint256 public count; + + function getCount() public view returns (uint256) { + return count; + } + + function increment() public { + count += 1; + } + + function decrement() public { + require(count > 0, "Count cannot go below zero"); + count -= 1; + } + + function reset() public { + count = 0; + } +} diff --git a/week-05/theory/quiz-05-solution.md b/week-05/theory/quiz-05-solution.md new file mode 100644 index 0000000..ff4b9cc --- /dev/null +++ b/week-05/theory/quiz-05-solution.md @@ -0,0 +1,425 @@ +# Week 5 Quiz: PoS/Consensus + RainbowKit + +> **제출 방법:** 이 파일을 복사하여 답변을 작성한 후, PR로 제출하세요. +> **평가 기준:** 개념 이해도 중심 - 문법 오류보다 논리적 설명을 중시합니다. + +--- + +## 문제 1: PoS 개념 (객관식) + +이더리움이 PoW(작업 증명)에서 PoS(지분 증명)로 전환한 **가장 주요한 이유**는 무엇인가요? + +**보기:** +A) 트랜잭션 처리 속도를 10배 이상 높이기 위해 +B) 에너지 소비를 99.95% 이상 줄이고 환경 친화적으로 만들기 위해 +C) 블록 크기를 늘려서 더 많은 데이터를 저장하기 위해 +D) 채굴 장비 없이도 누구나 블록을 생성할 수 있게 하기 위해 + +**답변:** +B) PoW는 채굴 과정에서 대량의 전력을 소비하는 것이 가장 큰 문제였다. PoS는 연산 경쟁 대신 ETH 스테이킹으로 블록을 검증하므로 에너지 소비를 99.95% 이상 절감하여 환경 친화적으로 만든 것이 핵심 이유이다. + + +--- + +## 문제 2: 검증자 역할 (객관식) + +이더리움 PoS에서 검증자(Validator)가 수행하는 **두 가지 주요 역할**은 무엇인가요? + +**보기:** +A) 블록 채굴(Mining)과 가스 가격 결정 +B) 블록 제안(Proposing)과 블록 증명(Attesting) +C) 트랜잭션 전송과 수수료 수집 +D) 스마트 컨트랙트 배포와 실행 + +**답변:** +B) 블록 제안은 선택된 검증자가 새로운 블록을 생성하여 네트워크에 제안하는 역할이다. 블록 증명은 나머지 검증자들이 제안된 블록이 유효한지 확인하고 투표하는 역할이다. + + +--- + +## 문제 3: 왜 PoW에서 PoS로? (단답형) + +PoW(작업 증명)와 PoS(지분 증명)의 **핵심 차이점**은 무엇인가요? +"자격 증명 방식"과 "보안 보장 방식" 두 관점에서 각각 비교하세요. + +**답변:** + +자격 증명 방식: +- PoW: 해시 연산 경쟁에서 가장 먼저 정답을 찾은 채굴자가 블록 생성 자격을 얻는다. +- PoS: 32 ETH를 스테이킹한 검증자 중 무작위로 선택된 자가 블록 제안 자격을 얻는다. + +보안 보장 방식: +- PoW: 네트워크 공격을 위해 전체 해시파워의 51% 이상을 확보해야 하므로 막대한 하드웨어/전력 비용이 공격 억제력이 된다. +- PoS: 공격을 시도하면 스테이킹한 ETH가 소각되므로 경제적 손실이 공격 억제력이 된다. + + +--- + +## 문제 4: 슬래싱의 목적 (단답형) + +슬래싱(Slashing)은 검증자의 스테이킹된 ETH를 **강제로 소각**하는 패널티입니다. + +1) 슬래싱이 발동되는 **두 가지 조건**은 무엇인가요? +2) **왜** 이런 처벌이 필요한가요? 없다면 어떤 문제가 생길 수 있나요? + +**답변:** + +1) 슬래싱 조건 (2가지): + - 이중 투표: 같은 슬롯에서 서로 다른 두 블록에 동시에 투표하는 행위이다. + - 서라운드 투표: 이전 증명을 감싸는 모순된 증명을 제출하는 행위이다. + +2) 슬래싱이 필요한 이유: + 슬래싱이 없으면 검증자가 아무런 경제적 위험 없이 악의적 행동을 시도할 수 있다. 슬래싱은 부정 행위 시 스테이킹한 ETH를 잃게 함으로써 공격 비용을 극대화하고, 검증자가 정직하게 행동하도록 경제적 인센티브를 제공한다. + + +--- + +## 문제 5: 체인 선택 규칙 (단답형) + +여러 유효한 블록이 동시에 제안되면 **포크(Fork)**가 발생합니다. +이더리움의 LMD-GHOST(Latest Message Driven GHOST) 규칙은 어떻게 "정규 체인"을 선택하나요? + +1) LMD-GHOST의 기본 원리는 무엇인가요? +2) **왜** "가장 최근 메시지"를 사용하나요? (오래된 메시지를 사용하면 어떤 문제가?) + +**답변:** + +1) LMD-GHOST 원리: + 각 검증자의 가장 최근 증명(attestation)만 고려하여, 포크가 발생했을 때 가장 많은 증명 가중치(스테이킹 양)가 축적된 포크를 정규 체인으로 선택한다. + +2) 최근 메시지 사용 이유: + 오래된 메시지를 사용하면 검증자가 과거 투표를 재활용하여 이미 폐기된 포크에 허위 지지를 몰아줄 수 있다. 최신 메시지만 반영해야 검증자의 현재 의사를 정확히 반영하고 조작을 방지할 수 있다. + + +--- + +## 문제 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 +'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 RootLayout({ children }) { + return ( + + + + + + {children} + + + + + + ); +} +``` + +**왜 이 순서인가요:** +WagmiProvider가 가장 바깥에 있어야 블록체인 연결 설정을 하위 모든 Provider와 컴포넌트에 제공한다. QueryClientProvider는 wagmi의 데이터 패칭/캐싱을 처리해야 하므로 그 안에 위치한다. RainbowKitProvider는 wagmi와 react-query 모두에 의존하므로 가장 안쪽에 위치해야 한다. 순서가 잘못되면 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 순서가 완전히 뒤집혀 있다. WagmiProvider가 가장 안쪽에 있고, RainbowKitProvider가 WagmiProvider 바깥에 있다. + +**2) 왜 이것이 문제인가:** +RainbowKitProvider는 내부적으로 wagmi의 Context에 의존하는데, WagmiProvider보다 바깥에 있으므로 WagmiContext를 찾을 수 없다. 마찬가지로 wagmi 훅들은 QueryClient에 의존하는데 WagmiProvider 안쪽에 QueryClientProvider가 없으므로 데이터 패칭이 실패한다. + +**3) 올바른 수정 방법:** +```typescript +'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 ( + + + + {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 ( +
+ + + {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(); + + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ + hash, + }); + + return ( +
+ + + {isSuccess &&

트랜잭션 성공!

} +
+ ); +} +``` + +**트랜잭션 상태 흐름을 설명하세요:** + +1) isPending 상태: 사용자가 지갑(MetaMask 등)에서 트랜잭션 서명을 승인하기를 기다리는 단계이다. +2) isConfirming 상태: 서명이 완료되어 트랜잭션이 네트워크에 전송된 후, 블록에 포함(채굴)되기를 기다리는 단계이다. +3) isSuccess 상태: 트랜잭션이 블록에 포함되어 온체인에서 성공적으로 실행 완료된 상태이다. + + +--- + +## 문제 9: 검증자 생애주기 (다이어그램 해석) + +다음 다이어그램은 이더리움 검증자의 생애주기를 보여줍니다: + +```mermaid +stateDiagram-v2 + [*] --> Pending: 32 ETH 입금 + Pending --> Active: 활성화 큐 대기 + Active --> Slashed: 규칙 위반 + Active --> Exiting: 자발적 종료 + Exiting --> Exited: 출금 대기 + Slashed --> Exited: 강제 퇴장 + Exited --> [*]: ETH 출금 +``` + +**질문:** + +1) **Active** 상태에서 검증자가 수행하는 주요 활동은 무엇인가요? + +블록을 제안(Proposing)하고, 다른 검증자가 제안한 블록의 유효성을 증명(Attesting)하는 것이다. 이를 통해 네트워크의 합의에 참여하고 보상을 받는다. + +2) Active에서 **Slashed**로 전이되는 조건은 무엇인가요? 이 경우 검증자에게 어떤 일이 발생하나요? + +이중 투표(같은 슬롯에 두 블록 투표)나 서라운드 투표(모순된 증명 제출) 등의 규칙 위반 시 전이된다. 스테이킹한 ETH의 일부가 강제 소각되고, 검증자는 네트워크에서 강제 퇴장당한다. + +3) 검증자가 자발적으로 종료(**Exiting**)하려면 왜 바로 ETH를 출금할 수 없고 대기 기간이 필요한가요? + +대기 기간이 있어야 해당 검증자가 퇴장 전에 악의적 행동을 하지 않았는지를 네트워크가 검증할 수 있다. 또한 다수의 검증자가 동시에 빠져나가면 네트워크 보안이 약화되므로, 퇴장 속도를 조절하여 네트워크 안정성을 유지하기 위함이다. + + +--- + +## 문제 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**가 가장 바깥에 있어야 하는 이유는 무엇인가요? + +WagmiProvider는 블록체인 연결 설정(chain, transport 등)을 React Context로 제공하며, QueryClientProvider와 RainbowKitProvider 모두 이 Context에 의존하기 때문이다. 가장 바깥에 있어야 하위 모든 컴포넌트에서 wagmi 훅을 사용할 수 있다. + +2) **QueryClientProvider**의 역할은 무엇인가요? 없다면 어떤 문제가 발생하나요? + +QueryClientProvider는 TanStack Query(react-query)의 캐싱과 비동기 상태 관리를 담당한다. 없으면 wagmi의 데이터 패칭 훅(useBalance, useReadContract 등)이 동작하지 않고, 캐싱이 불가능하여 매번 중복 요청이 발생한다. + +3) 아래 코드에서 `useAccount()` hook이 **"Cannot find WagmiContext"** 오류를 발생시키는 이유는 무엇인가요? + +```typescript +// 오류 발생 코드 + + + {/* WagmiProvider가 안쪽에 있음 */} + {/* useAccount() 호출 */} + + + +``` + +RainbowKitProvider가 WagmiProvider보다 바깥에 위치해 있기 때문이다. RainbowKitProvider 내부에서 wagmi의 Context를 참조하려 하지만, 아직 WagmiProvider가 렌더링되지 않은 상태이므로 WagmiContext를 찾을 수 없다는 에러가 발생한다. + + +--- + +## 제출 전 체크리스트 + +- [x] 모든 문제에 답변을 작성했는가? +- [x] 객관식 문제: 정답 선택 **이유**를 설명했는가? +- [x] 단답형 문제: 2-3문장 이상으로 충분히 설명했는가? +- [x] 코드 문제: 완성된 코드와 **왜 그렇게 작성했는지** 설명했는가? +- [x] 다이어그램 문제: 각 질문에 논리적으로 답변했는가?