Web3Modal을 dApp에 통합하기
소개
Web3Modal은 개발자가 간단하고 사용자 정의 가능한 구성으로 dApp에서 여러 공급자를 지원할 수 있도록 도와주는 사용하기 쉬운 라이브러리입니다. 지갑 연결, 트랜잭션 수행, 계정 관리를 쉽게 할 수 있습니다.
이 가이드에서는 Web3Modal 라이브러리를 사용해 Kaikas, Klip, MetaMask, Coinbase Wallet 등 여러 지갑을 클레이튼 네트워크에 구축한 dApp에 통합하는 방법을 설명합니다.
전제 조건
- 작동하는 리액트 프로젝트(
npx create-react-app 프로젝트 이름
을 실행하여) - 필요한 지갑(Kaikas, Coinbase Wallet, MetaMask) 설치.
- RPC 엔드포인트: 지원되는 엔드포인트 공급자 중 하나에서 받을 수 있습니다.
- Faucet에서 KLAY 테스트: 충분한 KLAY로 계정에 자금을 충전합니다.
Web3Modal 및 지갑 공급자 옵션 설정하기
1단계: Web3Modal 및 이더리움 라이브러리 설치하기
블록체인과 상호작용하기 위해 Web3Modal과 원하는 라이브러리를 설치합니다. 이 튜토리얼에서는 Web3Modal에서 파생되어 Kaikas 지갑과 Klip 지갑을 추가하도록 수정된 @klaytn/web3modal을 설치하겠습니다. 또한, 이 튜토리얼에서는 ethers.js를 사용하여 Klaytn 블록체인과 상호작용할 것입니다.
npm install @klaytn/web3modalnpm install --save ethers
2단계: 지갑 공급자 옵션으로 Web3Modal 인스턴스화하기
원하는 지갑 서비스 제공업체를 설치합니다. 여기서는 Kaikas, Klip 및 Coinbase 지갑 공급업체를 설치합니다.
npm install --save @coinbase/wallet-sdknpm install --save @klaytn/kaikas-web3-providernpm install --save @klaytn/klip-web3-provider
App.js
파일에서 CoinbaseWalletSDK, KaikasWeb3Provider, KlipWeb3Provider를 임포트하고 다양한 프로바이더 옵션을 인스턴스화하여 여러분의 dApp과 연동합니다.
import CoinbaseWalletSDK from '@coinbase/wallet-sdk';import { KaikasWeb3Provider } from "@klaytn/kaikas-web3-provider";import { KlipWeb3Provider } from "@klaytn/klip-web3-provider";export const providerOptions = { coinbasewallet: { package: CoinbaseWalletSDK, options: { appName: "Web 3 Modal Demo", infuraId: process.env.INFURA_KEY } }, walletconnect: { package: WalletConnect, options: { infuraId: process.env.INFURA_KEY } }};const providerOptions = { coinbasewallet: { package: CoinbaseWalletSDK, // required options: { appName: "Web3Modal Klaytn dApp", // required infuraId: "NFURA_KEY", // required rpc: "https://klaytn-mainnet-rpc.allthatnode.com:8551", // Optional if `infuraId` is provided; otherwise it's required chainId: 1001, // Optional. It defaults to 1 if not provided darkMode: false // Optional. Use dark theme, defaults to false } }, klip: { package: KlipWeb3Provider, //required options: { bappName: "Web3Modal Klaytn dApp", //required rpcUrl: "https://klaytn-mainnet-rpc.allthatnode.com:8551" //required }}, kaikas: { package: KaikasWeb3Provider // required }};
3단계: Web3Modal 인스턴스화
그런 다음 공급자 옵션을 전달하여 Web3Modal을 인스턴스화합니다.
import Web3Modal from "@klaytn/web3modal";const web3Modal = new Web3Modal( { cacheProvider: true, providerOptions, } )
지갑 연결 설정하기
사용자 지갑에 연결을 설정하려면 Web3Modal 인스턴스에서 connect()
메서드를 호출합니다. 이 작업을 비동기 함수로 래핑하고 검색된 공급자를 상태에 저장하여 앱 전체에서 재사용하는 것이 좋습니다.
import { ethers } from 'ethers';import { useState } from 'react';function App() { const [provider, setProvider] = useState(); const connectWallet = async () => { try { const web3ModalProvider = await web3Modal.connect(); // this guide uses ethers version 6.3.0. const ethersProvider = new ethers.BrowserProvider(web3ModalProvider); // for ethers version below 6.3.0. // const provider = new ethers.providers.Web3Provider(web3ModalProvider); setProvider(web3ModalProvider); } catch (error) { console.error(error); } }; return ( <div className="App"> <button onClick={connectWallet}>Connect Wallet</button> </div> );}
유틸리티 함수 설정
이 가이드에서는 truncateAddress()
및 toHex()
와 같은 유틸리티 함수를 사용하겠습니다. truncateAddress()
함수는 유효한 주소를 받아 전달된 주소의 읽기 쉬운 형식을 반환합니다. 반면에 toHex()
함수는 숫자를 16진수로 변환합니다. 아래 단계는 프로젝트에서 유틸리티 함수를 설정하고 사용하는 방법을 보여줍니다.
1단계: src
루트 폴더에 utils.js
파일을 생성합니다.
새로 만든 utils.js 파일에 다음 코드를 붙여넣습니다.
export const truncateAddress = (address) => { if (!address) return "No Account"; const match = address.match( /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{4})$/ ); if (!match) return address; return `${match[1]}…${match[2]}`; }; export const toHex = (num) => { const val = Number(num); return "0x" + val.toString(16); };
2단계: App.js
파일에서 함수를 가져옵니다.
import { truncateAddress, toHex } from "./utils";
연결, 계정, 네트워크 정보에 액세스하기
현재 Web3Modal은 연결된 계정 및 네트워크 데이터 검색과 같은 이더리움 상호 작용에 대한 기본 지원을 제공하지 않습니다. 사용자의 주소나 연결된 네트워크 ID를 읽으려면 이더리움 라이브러리에 직접 해당 정보를 요청해야 합니다. 이 가이드에서는 ethers.js를 사용하여 해당 정보를 가져올 것입니다. 한 가지 방법은 사용자를 dApp에 연결할 때 이 데이터를 가져와 저장하는 것입니다.
const [provider, setProvider] = useState();const [account, setAccount] = useState();const [chainId, setChainId] = useState();const connectWallet = async () => { try { const web3ModalProvider = await web3Modal.connect(); // this guide uses ethers version 6.3.0. const ethersProvider = new ethers.BrowserProvider(web3ModalProvider); // for ethers version below 6.3.0. // const provider = new ethers.providers.Web3Provider(web3ModalProvider); const accounts = await ethersProvider.listAccounts(); const network = await ethersProvider.getNetwork(); setProvider(provider); if (accounts) setAccount(accounts[0]); setChainId(network.chainId.toString()); } catch (error) { console.error(error); }};return ( <div className="App"> <button onClick={connectWallet}>Connect Wallet</button> <div>Connected To Chain ID: ${chainId}</div> <div>Wallet Address: ${truncateAddress(account)}</div> </div>);
지갑 연결 해제하기
지갑과의 연결 해제는 web3Modal 인스턴스에서 clearCachedProvider()
메서드를 사용하여 수행됩니다. 또한 상태를 새로 고쳐 이전에 저장된 연결 데이터를 지우는 것도 좋은 방법 중 하나입니다.
function App() { const disconnect = async () => { await web3Modal.clearCachedProvider(); refreshState(); };// refresh stateconst refreshState = () => { setAccount(); setChainId();// make sure to add every other state variable declared here.} return ( <div className="App"> <button onClick={disconnect}>Disconnect</button> </div> );}
사용자가 상호작용할 때 dApp 상태가 변경된다는 점을 명심해야 하며, 이에 대응하여 릴리스되는 이벤트를 구독하는 것이 가장 좋습니다. 이러한 이벤트에 대한 구독으로 사용 효과 후크를 생성하여 변경 사항에 적절히 대응할 수 있도록 하세요.
useEffect(() => { if (provider?.on) { const handleAccountsChanged = (accounts) => { setAccount(accounts); }; const handleChainChanged = (chainId) => { setChainId(chainId); }; const handleDisconnect = () => { disconnect(); }; provider.on("accountsChanged", handleAccountsChanged); provider.on("chainChanged", handleChainChanged); provider.on("disconnect", handleDisconnect); return () => { if (provider.removeListener) { provider.removeListener("accountsChanged", handleAccountsChanged); provider.removeListener("chainChanged", handleChainChanged); provider.removeListener("disconnect", handleDisconnect); } }; } }, [provider]);
네트워크 전환 또는 사용자 지정 네트워크 추가
앞서 설명한 바와 같이 Web3Modal은 이더리움 상호작용을 기본적으로 지원하지 않습니다. 네트워크를 추가하거나 전환하려면 이더리움 라이 브러리에 직접 요청(EIP-3085 또는 EIP-3326을 통해)해야 합니다. 다음은 네트워크 전환을 요청하고 사용자 지갑에 해당 네트워크가 없는 경우 대체 네트워크로 추가하는 예시입니다:
const switchNetwork = async () => { if (!provider) return; try { await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: toHex(8217) }], }); } catch (switchError) { // This error code indicates that the chain has not been added to MetaMask. if (switchError.code === 4902) { try { await provider.request({ method: "wallet_addEthereumChain", params: [ { chainId: toHex(8217), chainName: "Klaytn TestNet", rpcUrls: ["https://klaytn-mainnet-rpc.allthatnode.com:8551"], blockExplorerUrls: ["https://baobob.scope.com/"], }, ], }); } catch (addError) { throw addError; } } } };return ( <div className="App"> <button onClick={switchNetwork}>Switch Network</button> </div>)
메시지 서명하기
공급자 및 서명자 객체를 초기화하면 사용자는 임의의 문자열에 서명할 수 있습니다.
// add to the existing useState hook.const [signedMessage, setSignedMessage] = useState("");const signMessage = async(e) => { e.preventDefault() if (!provider) return; try { const signature = await provider.request({ method: "personal_sign", params: [message, account] }); setSignedMessage(signature); } catch (error) { console.log(error); }} return ( <div className="App"> <form onSubmit={signMessage}> <input type="text" name="message" placeholder="Set message" required/> <input type="submit" value="Sign Message"/> </form> <div>SignedMessage: ${signedMessage}</div> </div> );
네이티브 트랜잭션 보내기
한 사용자에서 다른 사용자로 KLAY를 보내 는 것과 같은 네이티브 트랜잭션을 수행할 수 있습니다.
// add to the existing useState hook. const [txHash, setTxHash] = useState(); const sendKlay = async () => { if (!provider) return; const destination = “paste recipient address”; // this guide uses ethers version 6.3.0. const ethersProvider = new ethers.BrowserProvider(provider); // for ethers version below 6.3.0. // const provider = new ethers.providers.Web3Provider(provider); const signer = await ethersProvider.getSigner(); // Submit transaction to the blockchain and wait for it to be mined const tx = await signer.sendTransaction({ to: destination, value: ethers.parseEther("0.1"), maxPriorityFeePerGas: "5000000000", // Max priority fee per gas maxFeePerGas: "6000000000000", // Max fee per gas }) const receipt = await tx.wait(); setTxHash(receipt.hash)}return ( <div className="App"> <button onClick={sendKlay}>Send Klay</button> <div>Send-Klay Tx Hash : {txHash ? <a href={`https://baobab.klaytnscope.com/tx/${txHash}`} target="_blank">Klaytnscope</a> : ' ' } </div> </div>);
스마트 컨트랙트로 작업하기
Web3Modal 공급자 및 서명자 개체를 사용하면 블록체인에 배포된 스마트 컨트랙트에 쓰기 및 읽기와 같은 컨트랙트 상호 작용을 할 수 있습니다.
1. Writing to a Contract
// add to existing useState hook const [contractTx, setContractTx] = useState(); const writeToContract = async (e) => { e.preventDefault(); if (!provider) return; // this guide uses ethers version 6.3.0. const ethersProvider = new ethers.BrowserProvider(provider); // for ethers version below 6.3.0. // const provider = new ethers.providers.Web3Provider(provider); const signer = await ethersProvider.getSigner(); // Paste your contractABI const contractABI = [ { "inputs": [ { "internalType": "uint256", "name": "_initNum", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "retrieve", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "num", "type": "uint256" } ], "name": "store", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] // Paste your contract address const contractAddress = "0x3b01E4025B428fFad9481a500BAc36396719092C"; const contract = new ethers.Contract(contractAddress, contractABI, signer); const value = e.target.store_value.value; // Send transaction to smart contract to update message const tx = await contract.store(value); // Wait for transaction to finish const receipt = await tx.wait(); const result = receipt.hash; setContractTx(result) }return ( <div className="App"> <form onSubmit={writeToContract}> <input name="store_value" placeholder="Set contract value" required/> <input type="submit" value="Store"/> </form> <div>Write-to-contract Tx Hash: ${contractTx}</div> </div>)
2. Reading from a contract
// add to existing useState hook const [contractMessage, setContractMessage] = useState(); const readFromContract = async () => { if (!provider) { console.log("provider not initialized yet"); return; } // this guide uses ethers version 6.3.0. const ethersProvider = new ethers.BrowserProvider(provider); // for ethers version below 6.3.0. // const provider = new ethers.providers.Web3Provider(provider); // paste your contract ABI const contractABI = [ { "inputs": [ { "internalType": "uint256", "name": "_initNum", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "retrieve", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "num", "type": "uint256" } ], "name": "store", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] // paste your contract address const contractAddress = "0x3b01E4025B428fFad9481a500BAc36396719092C"; const contract = new ethers.Contract(contractAddress, contractABI, ethersProvider) // Reading a message from the smart contract const contractMessage = await contract.retrieve(); setContractMessage(contractMessage.toString()) } return ( <div className="App"> <button onClick={readFromContract}>Read From Contract</button> <div>Read-from-contract Message: ${contractMessage}</div> </div> )
문제 해결
Node fs error, add browser {fs: false} to package.json
Node fs error, add browser {fs: false} to package.json
이 문제는 Klip-web3-provider를 설치할 때 발생합니다. 이 문제를 해결하려면 다음 단계를 따르세요:
1단계: node_modules 폴더를 열고 탐색합니다. 아래와 같이 @Klaytn/klip-web3-provider 폴더를 찾아 패키지.json 파일로 이동합니다:
@klaytn/klip-web3-provider/node_modules/caver-js/packages/caver.ipfs/package.json
2단계: 아래 코드를 @klaytn/klip-web3-provider/node_modules/caver-js/packages/caver.ipfs/package.json 파일에 붙여넣습니다.
"browser": { "fs": false },
Polyfill node core module error
BREAKING CHANGES: webpack<5 used to include polyfills for node.js core modules by default.
이 오류는 웹팩 버전 5를 사용할 때 발생합니다. 이 버전에서는 NodeJS 폴리필이 더 이상 기본적으로 지원되지 않습니다. 이 문제를 해결하려면 이 가이드를 참조하세요.
다음 단계
Web3Modal에 대한 자세한 가이드는 Web3Modal 문서 및 Web3Modal GitHub 리포지토리를 참조하시기 바랍니다. Also, you can find the full implementation of the code for this guide on GitHub.