Web3-Onboard

Web3-Onboard를 dApp에 통합하기

소개

프로젝트와 개발자는 Web3-Onboard와 같은 도구를 활용하여 다양한 지갑을 탈중앙화 애플리케이션(dApp)에 빠르게 통합할 수 있습니다. Web3-Onboard의 도움으로 사용자 온보딩이 간소화되었습니다. Web3-Onboard는 여러 종류의 지갑을 지원하는 기능 뿐 아니라 사용자가 계정을 다른 체인이나 네트워크에 연결하고 실시간 거래 알림을 받을 수 있는 기능 등 다양한 기능을 제공합니다.

본 가이드에서는 Web3-Onboard 라이브러리를 사용해 여러 지갑(예: Coinbase Wallet, Metamask, WalletConnect)을 클레이튼 네트워크에서 구축한 dApp에 통합하는 방법을 설명합니다.

준비사항

시작하기

체인에 구애받지 않는 지갑 라이브러리인 Web3-Onboard는 모든 EVM 호환 네트워크를 지원하며, 라이브러리에 새로운 네트워크를 추가할 수 있는 유연성도 제공합니다. 본 가이드에서는 Web3-Onboard를 사용하여 Klaytn 메인넷인 Cypress와 클레이튼 테스트넷인 Baobab을 dApp에 추가해 보겠습니다. 이제 Web3-Onboard를 사용해 다중지갑 호환성을 클레이튼 네트워크에 구축된 dApp에 통합해 보겠습니다.

온보드 및 지갑 모듈 설정하기

1단계: @web3-onboard/core 설치

npm i @web3-onboard/core 

2단계: 지갑 모듈 가져오기 및 인스턴스화

이 단계에서 지갑 모듈을 사용하여 dApp에서 지원할 지갑을 원하는 만큼 추가할 수 있습니다. 하지만 본 가이드에서는 Web3-Onboard 구현에 Coinbase Wallet, WalletConnect, Injected Wallets을 추가해 보도록 하겠습니다. Web3-Onboard를 사용하여 dApp에 추가할 수 있는 지갑 모듈 목록은 이 docs을 참조하세요.

npm install @web3-onboard/coinbase // Coinbase Wallet
npm install @web3-onboard/walletconnect // WalletConnect
npm 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.jsweb3.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 파일의 앱 함수 내에서 onboard 인스턴스에서 connectWallet() 메서드를 호출하여 onboard 팝업 모달을 초기화합니다.

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 및 기타 인스턴스화된 지갑에 원활하게 연결할 수 있는 모달이 표시됩니다.

지갑 연결 해제하기

지갑 연결을 끊으려면 사용자의 primary wallet 레이블과 함께 onboard 인스턴스에서 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() 메서드를 사용하여 onboard 인스턴스를 통해 저장된 연결 상태를 가져올 수 있습니다. 초기 연결 중에 상태를 가져올 수도 있습니다. 이제 수정된 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>
    </3>
  );
}

네트워크 전환하기

사용자에게 네트워크를 전환하라는 메시지를 dApp에 표시할 수 있도록 Web3-Onboard는 초기화된 onboard 인스턴스에서 setChain 메서드를 제공합니다. 다만 애플리케이션을 시작할 때 대상 네트워크가 onboard 인스턴스로 초기화되어 있어야 합니다.

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.scope.klaytn.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>

  )

문제 해결

Polyfill node core module error

BREAKING CHANGES: webpack<5 used to include polyfills for node.js core modules by default.

이 오류는 Webpack 5 버전을 사용할 때 발생합니다. 이 버전에서는 NodeJS 폴리필이 더 이상 기본으로 지원되지 않습니다. 이 문제를 해결하려면 해당 가이드를 참조하세요.

다음 단계

Web3-Onboard에 대한 자세한 가이드는 Blocknative 문서Blocknative Github 리포지토리에서 확인할 수 있습니다. 또한 이 가이드에 대한 전체 코드 구현은 GitHub에서 확인할 수 있습니다.

Last updated