Leveraging a tool like Web3-Onboard, projects and developers may quickly integrate multiple wallets into their decentralized applications (dApps). With the help of Web3-Onboard, user onboarding has been simplified. Web3-Onboard does have different features, ranging from support for several wallets to the ability for users to connect their accounts to different chains or networks and receive real-time transaction notifications, et cetera.
In this guide, you will use Web3-Onboard library to integrate multiple wallets (such as Coinbase Wallet, Metamask, WalletConnect, 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.
Getting Started
Web3-Onboard as a chain-agnostic wallet library, supports all EVM-compatible networks and also provides the flexibility of adding new networks to the library. In this guide, we'll use Web3-Onboard to add the Klaytn Mainnet Cypress and Klaytn Testnet Baobab to our dApp. With that said, let’s get started integrating multi-wallet compatibility using Web3-Onboard into your dApp built on Klaytn Network.
Setting up Onboard and Wallet Modules
Step 1: Install @web3-onboard/core
npmi@web3-onboard/core
Step 2: Import and Instantiate Wallet Modules
In this step, you can add as many wallets to be supported in your dApp using the wallet modules. But for this guide, you will add Coinbase Wallet, WalletConnect, Injected Wallets to your web3-Onboard implementation. Refer to this docs for a list of wallet modules that can be added to your dApp using Web3-Onboard.
In your App.js file, instantiate the wallet modules to integrate with your dApp. Note that each module has its own unique options parameters to pass in, such as a fallback JSON RPC URL or default chain ID.
The Web3-Onboard provider can be used with libraries like ethers.js and web3.js. In this guide, we will use ethers.js to make Klaytn blockchain calls like getting the user's account, fetch balance, sign transaction, send transaction, read from and write to the smart contract.
npminstall--saveethers
In your App.js file, import the ethers package like this:
import { ethers } from"ethers";
Step 4: Import and Setup Web3ReactProvider
In this step, you will instantiate Onboard with the created modules and a list of chains to be compatible with the library. Open up your App.js file and paste the code below:
import Onboard from"@web3-onboard/core";constETH_MAINNET_RPC_URL=`Paste ETH RPC URL`;constKLAYTN_MAINNET_URL=`Paste KLAYTN MAINNET URL`constKLAYTN_BAOBAB_URL=`Paste KLAYTN BAOBAB URL`constonboard=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" } ] }});
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.
Once you click your Connect Wallet button, you should see a modal that allows you to seamlessly connect to Coinbase Wallet and other instantiated wallets from your dApp.
Disconnecting Wallet
Disconnecting a connected wallet can be achieved by calling the disconnectWallet() method on the onboard instance along with the label of the user's primary wallet. Also, one good practice is to refresh the state to clear any previously stored connection data.
functionApp() {constconnectWallet=async () => {try {constwallets=awaitonboard.connectWallet(); } catch (error) {console.error(error); } };constdisconnect=async () => {const [primaryWallet] =awaitonboard.state.get().wallets;if (primaryWallet) awaitonboard.disconnectWallet({ label:primaryWallet.label });refreshState(); };// refresh stateconstrefreshState= () => {setAccount("");setChainId("");setProvider();// make sure to add every other state declared here. };return ( <divclassName="App"> <buttononClick={connectWallet}>Connect Wallet</button> <buttononClick={disconnect}>Disconnect</button> </div> );}
Accessing connection, account, network information
After successfully connecting your wallet, you can use the onboard.state.get() method to fetch the state of your connection stored through the onboard instance. You can also fetch the state during the initial connection. Now you can modify the connectWallet() method to return a list of wallet states that you can store in your state and use throughout the application.
In order to prompt the user to switch networks in your dApps, Web3-Onboard provides a setChain method on an initialized instance of Onboard. Note that the target network must have been initialized with the onboard instance at the start of your application.
After successfully connecting to a wallet, you can store the provider object returned from the wallet connection in a state variable as done in connectWallet() function. You can therefore use this provider and signer object to send transactions to the blockchain.
// add to the existing useState hook.const [txHash,setTxHash] =useState();constsendKlay=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);constsigner=awaitethersProvider.getSigner();// Submit transaction to the blockchain and wait for it to be minedconsttx=awaitsigner.sendTransaction({ to:"0x75Bc50a5664657c869Edc0E058d192EeEfD570eb", 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>);
Interacting with Smart Contracts
With the Web3-Onboard provider and signer object, you can make contract interactions such as writing to and reading from a smart contract deployed on the blockchain.
// add to existing useState hookconst [contractTx,setContractTx] =useState();const [contractMessage,setContractMessage] =useState();constwriteToContract=async (e) => {e.preventDefault();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);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";// const contract = new Contract(contractAddress, contractABI, provider);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) }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"; // const contract = new Contract(contractAddress, contractABI, provider);constcontract=newethers.Contract(contractAddress, contractABI, ethersProvider)// Read message from smart contractconstcontractMessage=awaitcontract.retrieve();setContractMessage(contractMessage.toString()) }return ( <divclassName="App"> <formonSubmit={writeToContract}> <inputname="store_value"placeholder="Set contract value"required/> <inputtype="submit"value="Store"/> </form> <buttononClick={readFromContract}>Read From Contract</button> <div>Write-to-contract Tx Hash: ${contractTx}</div> <div>Read-from-contract Message: ${contractMessage}</div> </div> )
Troubleshooting
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.