+ {JSON.stringify(attrs.object, null, 2)}
+```
+
+대부분의 필드는 [`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp)를 사용하여 표시됩니다.
+
+```tsx
+
+ { funs.length > 0 &&
+ <>
+ Functions:
+ {message}
+ +{status}
+ + ++ {" "} + 🦊 + 브라우저에 가상 이더리움 지갑인 MetaMask를 설치해야 합니다. + +
+ + ), + } + } +} +``` + +이 거대한 코드 블록은 정확히 무엇을 하는 걸까요? + +먼저, 브라우저에서 `window.ethereum`이 활성화되어 있는지 확인합니다. + +`window.ethereum`은 MetaMask 및 기타 지갑 제공업체에서 주입하는 글로벌 API로, 웹사이트가 사용자의 이더리움 계정을 요청할 수 있도록 합니다. 승인되면 사용자가 연결된 블록체인에서 데이터를 읽고, 사용자에게 메시지 및 트랜잭션 서명을 제안할 수 있습니다. 자세한 내용은 [MetaMask 문서](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)를 확인하세요! + +`window.ethereum`이 _없으면_ MetaMask가 설치되지 않았다는 의미입니다. 그러면 `address`가 빈 문자열로 반환되고 `status` JSX 객체는 사용자가 MetaMask를 설치해야 한다는 것을 전달하는 JSON 객체가 반환됩니다. + +이제 `window.ethereum`이 _있으면_ 상황이 흥미로워집니다. + +try/catch 루프를 사용하여 [`window.ethereum.request({ method: "eth_requestAccounts" });`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts)를 호출하여 MetaMask에 연결을 시도합니다. 이 함수를 호출하면 브라우저에서 MetaMask가 열리고 사용자에게 지갑을 탈중앙화앱에 연결하라는 메시지가 표시됩니다. + +- 사용자가 연결하기로 선택하면 `method: "eth_requestAccounts"`는 탈중앙화앱에 연결된 모든 사용자의 계정 주소를 포함하는 배열을 반환합니다. 전체적으로 `connectWallet` 함수는 이 배열의 _첫 번째_ `address`\(9번째 줄 참조\)와 사용자에게 스마트 계약에 메시지를 작성하라는 `status` 메시지를 포함하는 JSON 객체를 반환합니다. +- 사용자가 연결을 거부하면 JSON 객체는 반환된 `address`에 대해 빈 문자열을 포함하고 사용자가 연결을 거부했음을 반영하는 `status` 메시지를 포함합니다. + +이제 이 `connectWallet` 함수를 작성했으므로 다음 단계는 `HelloWorld.js` 컴포넌트에서 호출하는 것입니다. + +#### `connectWallet` 함수를 `HelloWorld.js` UI 컴포넌트에 추가하기 {#add-the-connectWallet-function-to-your-HelloWorld-js-ui-component} + +`HelloWorld.js`의 `connectWalletPressed` 함수로 이동하여 다음과 같이 업데이트하세요. + +```javascript +// HelloWorld.js + +const connectWalletPressed = async () => { + const walletResponse = await connectWallet() + setStatus(walletResponse.status) + setWallet(walletResponse.address) +} +``` + +`interact.js` 파일에서 대부분의 기능이 `HelloWorld.js` 컴포넌트에서 어떻게 추상화되었는지 주목하세요. 이것은 M-V-C 패러다임을 준수하기 위함입니다! + +`connectWalletPressed`에서 가져온 `connectWallet` 함수를 await 호출하고, 그 응답을 사용하여 상태 훅을 통해 `status` 및 `walletAddress` 변수를 업데이트합니다. + +이제 두 파일(`HelloWorld.js` 및 `interact.js`)을 모두 저장하고 지금까지의 UI를 테스트해 보겠습니다. + +[http://localhost:3000/](http://localhost:3000/) 페이지에서 브라우저를 열고 페이지 오른쪽 상단의 "지갑 연결" 버튼을 누르세요. + +MetaMask가 설치되어 있다면, 지갑을 탈중앙화앱에 연결하라는 메시지가 표시됩니다. 연결 초대를 수락합니다. + +이제 지갑 버튼에 주소가 연결되었음이 반영되는 것을 볼 수 있습니다! 좋아요 🔥 + +다음으로, 페이지를 새로고침해 보세요... 이상하네요. 지갑이 이미 연결되어 있음에도 불구하고 지갑 버튼은 MetaMask에 연결하라는 메시지를 표시합니다... + +하지만 두려워하지 마세요! 이 주소 문제를 쉽게 해결할 수 있습니다(이해하셨나요?) `getCurrentWalletConnected`를 구현하여 주소가 이미 탈중앙화앱에 연결되어 있는지 확인하고 그에 따라 UI를 업데이트하면 됩니다! + +#### `getCurrentWalletConnected` 함수 {#the-getcurrentwalletconnected-function} + +`interact.js` 파일의 `getCurrentWalletConnected` 함수를 다음과 같이 업데이트하세요. + +```javascript +// interact.js + +export const getCurrentWalletConnected = async () => { + if (window.ethereum) { + try { + const addressArray = await window.ethereum.request({ + method: "eth_accounts", + }) + if (addressArray.length > 0) { + return { + address: addressArray[0], + status: "👆🏽 위 텍스트 필드에 메시지를 작성하세요.", + } + } else { + return { + address: "", + status: "🦊 오른쪽 상단 버튼을 사용하여 MetaMask에 연결하세요.", + } + } + } catch (err) { + return { + address: "", + status: "😥 " + err.message, + } + } + } else { + return { + address: "", + status: ( + ++ {" "} + 🦊 + 브라우저에 가상 이더리움 지갑인 MetaMask를 설치해야 합니다. + +
+ + ), + } + } +} +``` + +이 코드는 이전 단계에서 작성한 `connectWallet` 함수와 _매우_ 유사합니다. + +주요 차이점은 사용자가 지갑을 연결하기 위해 MetaMask를 여는 `eth_requestAccounts` 메서드를 호출하는 대신, 여기서는 현재 탈중앙화앱에 연결된 MetaMask 주소를 포함하는 배열을 반환하는 `eth_accounts` 메서드를 호출한다는 것입니다. + +이 함수가 작동하는 것을 보려면 `HelloWorld.js` 컴포넌트의 `useEffect` 함수에서 호출해 봅시다. + +```javascript +// HelloWorld.js + +useEffect(async () => { + const message = await loadCurrentMessage() + setMessage(message) + addSmartContractListener() + + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) +}, []) +``` + +`getCurrentWalletConnected` 호출의 응답을 사용하여 `walletAddress`와 `status` 상태 변수를 업데이트하는 것을 확인하세요. + +이제 이 코드를 추가했으니 브라우저 창을 새로고침해 봅시다. + +좋아요! 버튼에는 연결되었다고 표시되고 연결된 지갑 주소의 미리보기가 표시되어야 합니다 - 새로고침 후에도 말이죠! + +#### `addWalletListener` 구현하기 {#implement-addwalletlistener} + +탈중앙화앱 지갑 설정의 마지막 단계는 사용자가 연결을 끊거나 계정을 전환할 때와 같이 지갑의 상태가 변경될 때 UI가 업데이트되도록 지갑 리스너를 구현하는 것입니다. + +`HelloWorld.js` 파일에서 `addWalletListener` 함수를 다음과 같이 수정하세요. + +```javascript +// HelloWorld.js + +function addWalletListener() { + if (window.ethereum) { + window.ethereum.on("accountsChanged", (accounts) => { + if (accounts.length > 0) { + setWallet(accounts[0]) + setStatus("👆🏽 위 텍스트 필드에 메시지를 작성하세요.") + } else { + setWallet("") + setStatus("🦊 오른쪽 상단 버튼을 사용하여 MetaMask에 연결하세요.") + } + }) + } else { + setStatus( ++ {" "} + 🦊 + 브라우저에 가상 이더리움 지갑인 MetaMask를 설치해야 합니다. + +
+ ) + } +} +``` + +이 시점에서는 여기서 무슨 일이 일어나고 있는지 이해하는 데 도움이 필요 없을 것이라고 확신하지만, 철저함을 위해 간단히 분석해 보겠습니다. + +- 먼저, 함수는 `window.ethereum`이 활성화되어 있는지(즉, MetaMask가 설치되어 있는지) 확인합니다. + - 그렇지 않다면, `status` 상태 변수를 사용자가 MetaMask를 설치하도록 유도하는 JSX 문자열로 설정합니다. + - 활성화된 경우, 3번째 줄에서 `window.ethereum.on("accountsChanged")` 리스너를 설정하여 사용자가 탈중앙화앱에 추가 계정을 연결하거나 계정을 전환하거나 계정 연결을 끊는 등 MetaMask 지갑의 상태 변화를 수신합니다. 연결된 계정이 하나 이상 있는 경우, `walletAddress` 상태 변수는 리스너가 반환한 `accounts` 배열의 첫 번째 계정으로 업데이트됩니다. 그렇지 않으면 `walletAddress`는 빈 문자열로 설정됩니다. + +마지막으로 `useEffect` 함수에서 호출해야 합니다. + +```javascript +// HelloWorld.js + +useEffect(async () => { + const message = await loadCurrentMessage() + setMessage(message) + addSmartContractListener() + + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) + + addWalletListener() +}, []) +``` + +이것으로 끝입니다! 모든 지갑 기능을 성공적으로 프로그래밍했습니다! 이제 마지막 작업인 스마트 계약에 저장된 메시지 업데이트로 넘어갑시다! + +### 6단계: `updateMessage` 함수 구현하기 {#step-6-implement-the-updateMessage-function} + +자, 이제 마지막 단계에 도착했습니다! `interact.js` 파일의 `updateMessage`에서 다음을 수행할 것입니다. + +1. 스마트 계약에 게시하려는 메시지가 유효한지 확인합니다 +2. MetaMask를 사용하여 트랜잭션에 서명합니다 +3. `HelloWorld.js` 프런트엔드 컴포넌트에서 이 함수를 호출합니다 + +오래 걸리지 않을 것입니다. 이 탈중앙화앱을 완성합시다! + +#### 입력 오류 처리 {#input-error-handling} + +당연히 함수 시작 부분에 일종의 입력 오류 처리를 하는 것이 합리적입니다. + +MetaMask 확장 프로그램이 설치되지 않았거나, 지갑이 연결되지 않았거나(즉, 전달된 `주소`가 빈 문자열인 경우), `메시지`가 빈 문자열인 경우 함수가 조기에 반환되도록 하고 싶을 것입니다. `updateMessage`에 다음 오류 처리를 추가합시다. + +```javascript +// interact.js + +export const updateMessage = async (address, message) => { + if (!window.ethereum || address === null) { + return { + status: + "💡 블록체인에서 메시지를 업데이트하려면 MetaMask 지갑을 연결하세요.", + } + } + + if (message.trim() === "") { + return { + status: "❌ 메시지는 빈 문자열이 될 수 없습니다.", + } + } +} +``` + +이제 적절한 입력 오류 처리가 있으니, MetaMask를 통해 트랜잭션에 서명할 시간입니다! + +#### 트랜잭션 서명하기 {#signing-our-transaction} + +전통적인 웹3 이더리움 트랜잭션에 이미 익숙하다면 다음에 작성할 코드는 매우 친숙할 것입니다. 입력 오류 처리 코드 아래에 `updateMessage`에 다음을 추가합니다. + +```javascript +// interact.js + +//트랜잭션 매개변수 설정 +const transactionParameters = { + to: contractAddress, // 계약 게시 중을 제외하고 필수입니다. + from: address, // 사용자의 활성 주소와 일치해야 합니다. + data: helloWorldContract.methods.update(message).encodeABI(), +} + +//트랜잭션 서명 +try { + const txHash = await window.ethereum.request({ + method: "eth_sendTransaction", + params: [transactionParameters], + }) + return { + status: ( + + ✅{" "} + + Etherscan에서 트랜잭션 상태 보기! + +.env를 커밋하지 마세요! .env 파일을 다른 사람과 공유하거나 노출하지 마세요. 그렇게 하면 민감한 정보가 노출될 수 있습니다. 버전 관리 시스템을 사용하는 경우 .env를 gitignore 파일에 추가하세요.
+