Web3Modal is a simple-to-use library that helps developers add support for multiple providers in their dApps with a simple, customizable configuration. It makes connecting wallets, performing transactions, and managing accounts easy.
In this guide, you will use the web3Modal library to integrate multiple wallets such as Kaikas, Klip, Metamask, Coinbase Wallet, etc. into your dApp built on the Klaytn Network.
Prerequisite
A working react project (by executing npx create-react-app project-name)
RPC Endpoint: you can get this from one of the supported endpoint providers.
Test KLAY from Faucet: fund your account with sufficient KLAY.
Setting up Web3Modal and Wallet Provider Options
Step 1: Installing Web3Modal and an Ethereum library
Install web3Modal and your preferred library for interacting with the blockchain. In this tutorial, we will be installing @klaytn/web3modal which was derived from Web3Modal and modified to add Kaikas wallet and Klip wallet. Also, this tutorial will use ethers.js to interact with the Klaytn blockchain.
npminstall@klaytn/web3modalnpminstall--saveethers
Step 2: Instantiating Web3Modal with wallet provider options
Install the wallet providers of your choice. Here we install Kaikas, Klip and Coinbase wallet providers.
In your App.js file, import CoinbaseWalletSDK, KaikasWeb3Provider, and KlipWeb3Provider, and instantiate the various provider options to integrate with your dapp.
To establish a connection to the user’s wallet, call the connect() method on the Web3Modal instance. We recommend you to wrap this operation around an async function and store the retrieved provider in your state to reuse throughout the app.
import { ethers } from'ethers';import { useState } from'react';functionApp() {const [provider,setProvider] =useState();constconnectWallet=async () => {try {constweb3ModalProvider=awaitweb3Modal.connect();// this guide uses ethers version 6.3.0.constethersProvider=newethers.BrowserProvider(web3ModalProvider);// for ethers version below 6.3.0.// const provider = new ethers.providers.Web3Provider(web3ModalProvider);setProvider(web3ModalProvider); } catch (error) {console.error(error); } };return ( <divclassName="App"> <buttononClick={connectWallet}>Connect Wallet</button> </div> );}
Setting up Utils function
In this guide, we will be making use of the utils functions such as truncateAddress() and toHex(). The truncateAddress() function takes in a valid address and returns a more readable format of the address passed in. While the toHex() function converts numbers to hexadecimal. The following steps below show how to set up and use the utils function in your project.
Step 1: Create a utils.js file in the src root folder.
Paste the following code in the newly created utils.js file.
Accessing connection, account, network information
As it is, Web3Modal does not provide built-in support for Ethereum interactions, such as retrieving connected accounts and network data. Note that to read the user’s address or connected network ID, you must directly request the information from your Ethereum library. In this guide, we’ll be getting that information using ethers.js. One way is to fetch and store this data is when connecting your user to your dapp.
const [provider,setProvider] =useState();const [account,setAccount] =useState();const [chainId,setChainId] =useState();constconnectWallet=async () => {try {constweb3ModalProvider=awaitweb3Modal.connect();// this guide uses ethers version 6.3.0.constethersProvider=newethers.BrowserProvider(web3ModalProvider);// for ethers version below 6.3.0.// const provider = new ethers.providers.Web3Provider(web3ModalProvider);constaccounts=awaitethersProvider.listAccounts();constnetwork=awaitethersProvider.getNetwork();setProvider(provider);if (accounts) setAccount(accounts[0]);setChainId(network.chainId.toString()); } catch (error) {console.error(error); }};return ( <divclassName="App"> <buttononClick={connectWallet}>Connect Wallet</button> <div>Connected To Chain ID: ${chainId}</div> <div>Wallet Address: ${truncateAddress(account)}</div> </div>);
Disconnecting Wallet
Disconnecting from the wallet is achieved by using the clearCachedProvider() method on the web3Modal instance. Also, one good practice is to refresh the state to clear any previously stored connection data.
functionApp() {constdisconnect=async () => {awaitweb3Modal.clearCachedProvider();refreshState(); };// refresh stateconstrefreshState= () => {setAccount();setChainId();// make sure to add every other state variable declared here.}return ( <divclassName="App"> <buttononClick={disconnect}>Disconnect</button> </div> );}
It's important to keep in mind that the dApp state changes as users interact with it, and it's best practice to subscribe to the events that are released in response. Create useEffect hooks with subscriptions to these events so they can respond appropriately to changes.
As established previously, Web3Modal does not have built-in support for Ethereum interactions. In order to add or switch networks, you must directly make a request (via EIP-3085 or EIP-3326) to your Ethereum library. Here is an example of requesting to switch networks and adding the network as a fallback if it is not already present on the user’s wallet:
constswitchNetwork=async () => {if (!provider) return;try {awaitprovider.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 {awaitprovider.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 ( <divclassName="App"> <buttononClick={switchNetwork}>Switch Network</button> </div>)
Signing Messages
Having initialised the provider and signer object, users can sign an arbitrary string.
You can perform native transactions, like sending KLAY from one user to another.
// add to the existing useState hook.const [txHash,setTxHash] =useState();constsendKlay=async () => {if (!provider) return;constdestination= “paste recipient address”;// this guide uses ethers version 6.3.0.constethersProvider=newethers.BrowserProvider(provider);// for ethers version below 6.3.0.// const provider = new ethers.providers.Web3Provider(provider);constsigner=awaitethersProvider.getSigner();// Submit transaction to the blockchain and wait for it to be minedconsttx=awaitsigner.sendTransaction({ to: destination, value:ethers.parseEther("0.1"), maxPriorityFeePerGas:"5000000000",// Max priority fee per gas maxFeePerGas:"6000000000000",// Max fee per gas })constreceipt=awaittx.wait();setTxHash(receipt.hash)}return ( <divclassName="App"> <buttononClick={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>);
Working with a smart contract
With the Web3Modal provider and signer object, you can make contract interactions such as writing to and reading from a smart contract deployed to the blockchain.
Writing to a Contract
// add to existing useState hookconst [contractTx,setContractTx] =useState();constwriteToContract=async (e) => {e.preventDefault();if (!provider) return;// this guide uses ethers version 6.3.0.constethersProvider=newethers.BrowserProvider(provider);// for ethers version below 6.3.0.// const provider = new ethers.providers.Web3Provider(provider);constsigner=awaitethersProvider.getSigner();// Paste your contractABIconstcontractABI= [ {"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 addressconstcontractAddress="0x3b01E4025B428fFad9481a500BAc36396719092C";constcontract=newethers.Contract(contractAddress, contractABI, signer);constvalue=e.target.store_value.value;// Send transaction to smart contract to update messageconsttx=awaitcontract.store(value);// Wait for transaction to finishconstreceipt=awaittx.wait();constresult=receipt.hash;setContractTx(result) }return ( <divclassName="App"> <formonSubmit={writeToContract}> <inputname="store_value"placeholder="Set contract value"required/> <inputtype="submit"value="Store"/> </form> <div>Write-to-contract Tx Hash: ${contractTx}</div> </div>)
Reading from a contract
// add to existing useState hookconst [contractMessage,setContractMessage] =useState();constreadFromContract=async () => {if (!provider) {console.log("provider not initialized yet");return; }// this guide uses ethers version 6.3.0.constethersProvider=newethers.BrowserProvider(provider);// for ethers version below 6.3.0.// const provider = new ethers.providers.Web3Provider(provider);// paste your contract ABIconstcontractABI= [ {"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 addressconstcontractAddress="0x3b01E4025B428fFad9481a500BAc36396719092C"; constcontract=newethers.Contract(contractAddress, contractABI, ethersProvider)// Reading a message from the smart contractconstcontractMessage=awaitcontract.retrieve();setContractMessage(contractMessage.toString()) }return ( <divclassName="App"> <buttononClick={readFromContract}>Read From Contract</button> <div>Read-from-contract Message: ${contractMessage}</div> </div> )
TroubleShooting
Node fs error, add browser {fs: false} to package.json
Nodefserror,addbrowser{fs:false}topackage.json
This occurs when you install Klip-web3-provider. To fix this issue, follow these steps:
Step 1: Open up and navigate to your node_modules folder. Look for the @Klaytn/klip-web3-provider folder and navigate to it's package.json file as shown below:
Step 2: Paste the code below in @klaytn/klip-web3-provider/node_modules/caver-js/packages/caver.ipfs/package.json file.
"browser": {"fs":false },
Polyfill node core module error
BREAKING CHANGES: webpack<5 used to include polyfills for node.js core modules by default.
This error occurs when you use webpack version 5. In this version, NodeJS polyfills is no longer supported by default. To solve this issue, refer to this guide.