Web3-Onboard를 dApp에 통합하기
소개
프로젝트와 개발자는 Web3-Onboard와 같은 도구를 활용하여 여러 지갑을 탈중앙화 애플리케이션(dApp)에 빠르게 통합할 수 있습니다. Web3-Onboard의 도움으로 사용자 온보딩이 간소화되었습니다. Web3-Onboard는 여러 지갑 지원부터 사용자가 자신의 계정을 다른 체인이나 네트워크에 연결하고 실시간 거래 알림을 받을 수 있는 기능 등 다양한 기능을 갖추고 있습니다.
이 가이드에서는 Web3-Onboard 라이브러리를 사용해 여러 지갑(예: Coinbase Wallet, MetaMask, WalletConnect 등) 을 통합하는 방법, 즉, 클레이튼 네트워크에 구축한 dApp에 통합하는 방법을 설명합니다.
전제 조건
- 작동하는 리액트 프로젝트(
npx create-react-app project-name
실행) - 필요한 지갑 설치(Coinbase Wallet, MetaMask).
- RPC 엔드포인트: 지원되는 엔드포인트 공급자 중 하나에서 얻을 수 있습니다.
- Faucet에서 KLAY 테스트: 충분한 KLAY로 계정에 자금을 충전합니다.
시작하기
체인에 구애받지 않는 지갑 라이브러리인 Web3-Onboard는 모든 EVM 호환 네트워크를 지원하며, 라이브러리에 새로운 네트워크를 추가할 수 있는 유연성도 제공합니다. 이 가이드에서는 Web3-Onboard를 사용해 Klaytn 메인넷 Cypress와 클레이튼 테스트넷 Baobab을 dApp에 추가해 보겠습니다. 이제 Web3-Onboard를 사용해 다중지갑 호환성을 클레이튼 네트워크에 구축한 dApp에 통합해 보겠습니다.
온보드 및 지갑 모듈 설정
1단계: @web3-onboard/core 설치
npm i @web3-onboard/core
2단계: 지갑 모듈 가져오기 및 인스턴스화
이 단계에서는 지갑 모듈을 사용하여 dApp에서 지원할 지갑을 얼마든지 추가할 수 있습니다. 하지만 이 가이드에서는 웹 3.0 온보드 구현에 Coinbase Wallet, WalletConnect, Injected Wallet을 추가할 것입니다. Web3-Onboard를 사용하여 dApp에 추가할 수 있는 지갑 모듈 목록은 이 문서를 참조하세요.
npm install @web3-onboard/coinbase // Coinbase Walletnpm install @web3-onboard/walletconnect // WalletConnectnpm install @web3-onboard/injected-wallets // Used to connect to Metamask
App.js
파일에서 지갑 모듈을 인스턴스화하여 dApp과 통합합니다. 각 모듈에는 폴백 JSON RPC URL이나 기본 체인 ID와 같은 고유한 옵션 매개변수를 전달할 수 있다는 점에 유의하세요.
import coinbaseWalletModule from "@web3-onboard/coinbase";import walletConnectModule from "@web3-onboard/walletconnect";import injectedModule from "@web3-onboard/injected-wallets";const coinbaseWalletSdk = coinbaseWalletModule();const walletConnect = walletConnectModule();const injected = injectedModule();const modules = [coinbaseWalletSdk, walletConnect, injected];
3단계: ethers 설치 및 가져오기
Web3-Onboard 공급자는 ethers.js, web3.js와 같은 라이브러리와 함께 사용할 수 있습니다. 이 가이드에서는 ethers.js를 사용하여 사용자 계정 가져오기, 잔액 가져오기, 트랜잭션 서명, 트랜잭션 보내기, 스마트 컨트랙트 읽기 및 쓰기와 같은 Klaytn 블록체인 호출을 수행하겠습니다.
npm install --save ethers
App.js
파일에서 다음과 같이 ethers 패키지를 임포트합니다:
import { ethers } from "ethers";
4단계: Web3ReactProvider 가져오기 및 설정하기
이 단계에서는 생성된 모듈과 라이브러리와 호환될 체인 목록으로 Onboard를 인스턴스화합니다. App.js
파일을 열고 아래 코드를 붙여넣습니다:
import Onboard from "@web3-onboard/core";const ETH_MAINNET_RPC_URL = `Paste ETH RPC URL`;const KLAYTN_MAINNET_URL = `Paste KLAYTN MAINNET URL`const KLAYTN_BAOBAB_URL = `Paste KLAYTN BAOBAB URL`const onboard = Onboard({ wallets: modules, // created in previous step chains: [ { id: "0x1", // chain ID must be in hexadecimal token: "ETH", namespace: "evm", label: "Ethereum Mainnet", rpcUrl: ETH_MAINNET_RPC_URL }, { id: "0x2019", // chain ID must be in hexadecimal token: "KLAY", namespace: "evm", label: "Klaytn Mainnet", rpcUrl: KLAYTN_MAINNET_URL }, { id: "0x3e9", // chain ID must be in hexadecimel token: "KLAY", namespace: "evm", label: "Klaytn Testnet", rpcUrl: KLAYTN_BAOBAB_URL }, // you can add as much supported chains as possible ], appMetadata: { name: "Klaytn-web3-onboard-App", // change to your dApp name icon: "https://pbs.twimg.com/profile_images/1620693002149851137/GbBC5ZjI_400x400.jpg", // paste your icon logo: "https://pbs.twimg.com/profile_images/1620693002149851137/GbBC5ZjI_400x400.jpg", // paste your logo description: "Web3Onboard-Klaytn", recommendedInjectedWallets: [ { name: "Coinbase", url: "https://wallet.coinbase.com/" }, { name: "MetaMask", url: "https://metamask.io" } ] }});
유틸리티 함수 설정
이 가이드에서는 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";
지갑 연 결하기
App.js
파일의 앱 함수 내에서 온보드 인스턴스의 connectWallet()
메서드를 호출하여 온보드 팝업 모달을 시작하세요.
function App() { const connectWallet = async () => { try { const wallets = await onboard.connectWallet(); } catch (error) { console.error(error); } }; return ( <div className="App"> <button onClick={connectWallet}>Connect Wallet</button> </div> );}
Connect Wallet 버튼을 클릭하면 dApp에서 Coinbase Wallet 및 기타 인스턴스화된 지갑에 원활하게 연결할 수 있는 모달이 표시됩니다.
지갑 연결 해제하기
연결된 지갑의 연결을 끊으려면 온보드 인스턴스에서 사용자의 기본 지갑 레이블과 함께 disconnectWallet()
메서드를 호출하면 됩니다. 또한, 상태를 새로고침하여 이전에 저장된 연결 데이터를 지우는 것도 좋은 방법 중 하나입니다.
function App() { const connectWallet = async () => { try { const wallets = await onboard.connectWallet(); } catch (error) { console.error(error); } }; const disconnect = async () => { const [primaryWallet] = await onboard.state.get().wallets; if (primaryWallet) await onboard.disconnectWallet({ label: primaryWallet.label }); refreshState(); }; // refresh state const refreshState = () => { setAccount(""); setChainId(""); setProvider(); // make sure to add every other state declared here. }; return ( <div className="App"> <button onClick={connectWallet}>Connect Wallet</button> <button onClick={disconnect}>Disconnect</button> </div> );}
연결, 계정, 네트워크 정보에 액세스하기
지갑 연결에 성공하면 onboard.state.get() 메서드를 사용해 온보드 인스턴스를 통해 저장된 연결 상태를 가져올 수 있습니다. 초기 연결 중에 상태를 가져올 수도 있습니다. 이제 connectWallet() 메서드를 수정하여 지갑 상태에 저장하고 애플리케이션 전체에서 사용할 수 있는 지갑 상태 목록을 반환할 수 있습니다.
1단계: React의 useState 가져오기
import { useState } from 'react';
2단계: 앱 함수 내에서 코드 수정
function App() { const [provider, setProvider] = useState(); const [account, setAccount] = useState(); const [chainId, setChainId] = useState(); const connectWallet = async () => { try { const wallets = await onboard.connectWallet(); const { accounts, chains, provider } = wallets[0]; setProvider(provider); setAccount(accounts[0].address); setChainId(chains[0].id); } catch (error) { console.error(error); } }; ... return ( <div className="App"> <div> { !account ? ( <button onClick={connectWallet}> Connect Wallet</button> ) : ( <button onClick={disconnect}>Disconnect</button> )} </div> <div>Wallet Address: ${truncateAddress(account)}</div> <div>Network Chain ID: ${chainId}</div> </div> );}
네트워크 전환하기
사용자에게 dApp에서 네트워크를 전환하라는 메시지를 표시하기 위해 Web3-Onboard는 초기화된 온보드 인스턴스에서 setChain
메서드를 제공합니다. 애플리케이션을 시작할 때 대상 네트워크가 온보드 인스턴스로 초기화되어 있어야 합니다.
const switchNetwork = async () => {await onboard.setChain({ chainId: toHex(1001) });};return ( <div className="App"> <button onClick={switchNetwork}>Switch Network</button> </div>)
네이티브 트랜잭션 보내기
지갑에 성공적으로 연결한 후 지갑 연결에서 반환된 공급자 개체를 connectWallet() 함수에서 수행한 것처럼 상태 변수에 저장할 수 있습니다. 따라서 이 공급자 및 서명자 개체를 사용하여 블록체인에 트랜잭션을 전송할 수 있습니다.
// add to the existing useState hook. const [txHash, setTxHash] = useState(); const sendKlay = 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); const signer = await ethersProvider.getSigner(); // Submit transaction to the blockchain and wait for it to be mined const tx = await signer.sendTransaction({ to: "0x75Bc50a5664657c869Edc0E058d192EeEfD570eb", 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>);
스마트 컨트랙트와의 상호작용
Web3-Onboard 공급자 및 서명자 개체를 사용하면 블록체인에 배포된 스마트 컨트랙트에 쓰기 및 읽기와 같은 컨트랙트 상호 작용을 할 수 있습니다.
// add to existing useState hook const [contractTx, setContractTx] = useState(); const [contractMessage, setContractMessage] = useState(); const writeToContract = async (e) => { e.preventDefault(); 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); 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 Contract(contractAddress, contractABI, provider); 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) } 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 Contract(contractAddress, contractABI, provider); const contract = new ethers.Contract(contractAddress, contractABI, ethersProvider) // Read message from smart contract const contractMessage = await contract.retrieve(); setContractMessage(contractMessage.toString()) } return ( <div className="App"> <form onSubmit={writeToContract}> <input name="store_value" placeholder="Set contract value" required/> <input type="submit" value="Store"/> </form> <button onClick={readFromContract}>Read From Contract</button> <div>Write-to-contract Tx Hash: ${contractTx}</div> <div>Read-from-contract Message: ${contractMessage}</div> </div> )