1/5
## Connecting Your Dapp: Adding a web3 Wallet Button with JavaScript and Viem This lesson guides you through adding a "Connect Wallet" button to a basic HTML page. We'll start with fundamental JavaScript DOM manipulation and event handling, then check for a browser wallet like MetaMask, and finally use the modern `viem` library to robustly handle the connection process. ## Initial Setup and Selecting the Button First, ensure you have a basic HTML file (`index.html`) and a JavaScript file (`index-js.js`) linked together. Your HTML needs a button element we can interact with. **`index.html`:** ```html <!DOCTYPE html> <html> <head> <title>Connect Button Lesson</title> </head> <body> <button id="connectButton">Connect</button> <script src="index-js.js" type="module"></script> <!-- type="module" is needed for imports --> </body> </html> ``` Now, in your `index-js.js` file, we need to get a reference to this button using JavaScript's Document Object Model (DOM) API. The `document` object represents your HTML page, and its methods allow us to find and manipulate elements. **`index-js.js`:** ```javascript const connectButton = document.getElementById('connectButton'); ``` This code uses `document.getElementById` to find the HTML element with the ID "connectButton" and stores a reference to it in the constant `connectButton`. We can now use this variable to control the button's behavior. ## Adding Basic Click Functionality To make the button interactive, we attach an event listener. Specifically, we'll define a function to run whenever the button is clicked. **`index-js.js`:** ```javascript // Get the button element (from previous step) const connectButton = document.getElementById('connectButton'); // Define the function to run on click function connect() { console.log("Button clicked!"); // Simple log to test } // Assign the function to the button's onclick event connectButton.onclick = connect; ``` Here, we define a function named `connect`. For now, it just logs a message to the browser's developer console. We then assign this `connect` function to the `onclick` property of our `connectButton` element. Now, every time the button is clicked in the browser, the `connect` function will execute, and you'll see "Button clicked!" in the console. ## Checking for MetaMask (or Similar Wallets) Before attempting to connect a wallet, we must check if one is available in the user's browser environment. Ethereum wallets like MetaMask inject a global JavaScript object, typically `window.ethereum`, into the browser. We can check for its existence. Let's modify the `connect` function: **`index-js.js`:** ```javascript const connectButton = document.getElementById('connectButton'); function connect() { // Check if window.ethereum is present if (typeof window.ethereum !== "undefined") { // Wallet is likely installed console.log("MetaMask (or compatible wallet) is available!"); // We'll add connection logic here later } else { // Wallet is not installed console.log("No wallet detected."); connectButton.innerHTML = "Please install MetaMask!"; // Update button text } } connectButton.onclick = connect; ``` Inside `connect`, we use `typeof window.ethereum !== "undefined"` to see if the `ethereum` object exists on the `window`. If it does, we log a success message (for now). If it doesn't, we log a different message and update the button's text (`innerHTML`) to prompt the user to install a wallet. Test this in browsers with and without MetaMask installed to see the difference. ## Introducing Wallet Abstraction with Viem While directly using `window.ethereum` works for basic checks and connections with MetaMask-compatible wallets, it's often better to use a wallet abstraction library. These libraries provide a standardized way to interact with various types of wallets and blockchain functionalities, making your code more robust, maintainable, and compatible with different wallet providers. Two popular choices are `ethers.js` and `viem`. We'll use `viem` (`viem.sh`) for this tutorial due to its modern design, type safety, and lightweight nature. Instead of installing `viem` via `npm` (common in larger projects), we'll import it directly from a Content Delivery Network (CDN) using an ES Module import, which works well for simple HTML/JS setups. Make sure your `<script>` tag in `index.html` has `type="module"`. **`index-js.js`:** ```javascript // Import necessary functions from viem via CDN import { createWalletClient, custom } from "https://esm.sh/viem"; const connectButton = document.getElementById('connectButton'); // Declare walletClient variable outside the function // so it can be potentially accessed elsewhere if needed let walletClient; function connect() { if (typeof window.ethereum !== "undefined") { console.log("MetaMask (or compatible wallet) is available!"); // Connection logic using viem will go here } else { console.log("No wallet detected."); connectButton.innerHTML = "Please install MetaMask!"; } } connectButton.onclick = connect; ``` We import `createWalletClient` (to create an interface for wallet actions) and `custom` (to specify how to communicate with the wallet) from `viem`. ## Connecting the Wallet using Viem Now, let's use `viem` inside our `if` block to handle the actual connection request. In `viem`, we create a `WalletClient`. This client needs a "Transport" to define *how* it communicates. Since we want to use the browser's injected wallet (MetaMask), we use the `custom` transport configured with `window.ethereum`. Once we have the client, we can call its `requestAddresses` method. This method corresponds to the standard `eth_requestAccounts` RPC call, which prompts the user to connect their wallet. **`index-js.js` (inside the `connect` function's `if` block):** ```javascript // Inside the 'if (typeof window.ethereum !== "undefined")' block: console.log("Connecting using viem..."); // Create a Wallet Client walletClient = createWalletClient({ transport: custom(window.ethereum) // Use the browser's injected provider }); // Request wallet connection (account addresses) walletClient.requestAddresses(); console.log("Connection request sent..."); // This logs too early! (See next section) connectButton.innerHTML = "Connected!"; // This updates too early! ``` If you run this code and click connect, MetaMask *will* pop up asking for permission. However, you'll notice the console logs "Connection request sent..." and the button text changes to "Connected!" *immediately*, even before you interact with the MetaMask pop-up. This happens because `requestAddresses` is an asynchronous operation. ## Handling Asynchronicity with `async`/`await` Wallet interactions and network requests are asynchronous: they don't block the rest of your code while waiting for a response (like user confirmation in MetaMask). They return a `Promise`, which represents a future result. To make our code wait for the asynchronous operation to complete before proceeding, we use the `async` and `await` keywords. 1. Mark the function containing the asynchronous call as `async`. 2. Use the `await` keyword before the asynchronous call (`walletClient.requestAddresses()`). **`index-js.js` (Refactored `connect` function):** ```javascript import { createWalletClient, custom } from "https://esm.sh/viem"; const connectButton = document.getElementById('connectButton'); let walletClient; // Make the function async async function connect() { if (typeof window.ethereum !== "undefined") { console.log("Connecting..."); walletClient = createWalletClient({ transport: custom(window.ethereum), }); try { // Wait for the user to connect their wallet const addresses = await walletClient.requestAddresses(); console.log("Connected accounts:", addresses); // Log the connected address(es) // This code now runs ONLY AFTER the await completes successfully connectButton.innerHTML = `Connected: ${addresses[0].slice(0, 6)}...`; // Show part of address console.log("Connection successful!"); } catch (error) { // Handle errors, like the user rejecting the connection console.error("Connection failed:", error); connectButton.innerHTML = "Connect"; // Reset button text on failure } } else { connectButton.innerHTML = "Please install MetaMask!"; } } connectButton.onclick = connect; ``` Now, when `connect` is called: 1. It checks for `window.ethereum`. 2. It creates the `walletClient`. 3. It hits `await walletClient.requestAddresses()`. The function execution *pauses* here. 4. MetaMask (or the relevant wallet) prompts the user. 5. If the user approves, the `Promise` returned by `requestAddresses` resolves, `await` gets the result (an array of addresses), and execution continues. The button text is updated, and success is logged. 6. If the user rejects or an error occurs, the `catch` block executes, logging the error and resetting the button text. ## Important Considerations * **Brave Shields:** If using the Brave browser, ensure "Shields" are down for your development environment (e.g., `localhost` or `127.0.0.1`) as they can sometimes interfere with wallet detection and interaction. * **Asynchronicity is Crucial:** Understanding `async`/`await` and Promises is fundamental in web3, as nearly all interactions with wallets and blockchains are asynchronous. * **Error Handling:** The `try...catch` block is essential for a good user experience. Always handle potential errors, such as the user cancelling the connection request. * **Viem Benefits:** Using `viem` provides type safety, better abstraction, and helper functions compared to directly using `window.ethereum`, making development smoother, especially for more complex applications. You have now successfully implemented a "Connect Wallet" button that uses modern JavaScript and the `viem` library to interact with browser-based Ethereum wallets.
This lesson guides you through adding a "Connect Wallet" button to a basic HTML page. We'll start with fundamental JavaScript DOM manipulation and event handling, then check for a browser wallet like MetaMask, and finally use the modern viem
library to robustly handle the connection process.
First, ensure you have a basic HTML file (index.html
) and a JavaScript file (index-js.js
) linked together. Your HTML needs a button element we can interact with.
index.html
:
Now, in your index-js.js
file, we need to get a reference to this button using JavaScript's Document Object Model (DOM) API. The document
object represents your HTML page, and its methods allow us to find and manipulate elements.
index-js.js
:
This code uses document.getElementById
to find the HTML element with the ID "connectButton" and stores a reference to it in the constant connectButton
. We can now use this variable to control the button's behavior.
To make the button interactive, we attach an event listener. Specifically, we'll define a function to run whenever the button is clicked.
index-js.js
:
Here, we define a function named connect
. For now, it just logs a message to the browser's developer console. We then assign this connect
function to the onclick
property of our connectButton
element. Now, every time the button is clicked in the browser, the connect
function will execute, and you'll see "Button clicked!" in the console.
Before attempting to connect a wallet, we must check if one is available in the user's browser environment. Ethereum wallets like MetaMask inject a global JavaScript object, typically window.ethereum
, into the browser. We can check for its existence.
Let's modify the connect
function:
index-js.js
:
Inside connect
, we use typeof window.ethereum !== "undefined"
to see if the ethereum
object exists on the window
. If it does, we log a success message (for now). If it doesn't, we log a different message and update the button's text (innerHTML
) to prompt the user to install a wallet. Test this in browsers with and without MetaMask installed to see the difference.
While directly using window.ethereum
works for basic checks and connections with MetaMask-compatible wallets, it's often better to use a wallet abstraction library. These libraries provide a standardized way to interact with various types of wallets and blockchain functionalities, making your code more robust, maintainable, and compatible with different wallet providers.
Two popular choices are ethers.js
and viem
. We'll use viem
(viem.sh
) for this tutorial due to its modern design, type safety, and lightweight nature.
Instead of installing viem
via npm
(common in larger projects), we'll import it directly from a Content Delivery Network (CDN) using an ES Module import, which works well for simple HTML/JS setups. Make sure your <script>
tag in index.html
has type="module"
.
index-js.js
:
We import createWalletClient
(to create an interface for wallet actions) and custom
(to specify how to communicate with the wallet) from viem
.
Now, let's use viem
inside our if
block to handle the actual connection request.
In viem
, we create a WalletClient
. This client needs a "Transport" to define how it communicates. Since we want to use the browser's injected wallet (MetaMask), we use the custom
transport configured with window.ethereum
.
Once we have the client, we can call its requestAddresses
method. This method corresponds to the standard eth_requestAccounts
RPC call, which prompts the user to connect their wallet.
index-js.js
(inside the connect
function's if
block):
If you run this code and click connect, MetaMask will pop up asking for permission. However, you'll notice the console logs "Connection request sent..." and the button text changes to "Connected!" immediately, even before you interact with the MetaMask pop-up. This happens because requestAddresses
is an asynchronous operation.
async
/await
Wallet interactions and network requests are asynchronous: they don't block the rest of your code while waiting for a response (like user confirmation in MetaMask). They return a Promise
, which represents a future result.
To make our code wait for the asynchronous operation to complete before proceeding, we use the async
and await
keywords.
Mark the function containing the asynchronous call as async
.
Use the await
keyword before the asynchronous call (walletClient.requestAddresses()
).
index-js.js
(Refactored connect
function):
Now, when connect
is called:
It checks for window.ethereum
.
It creates the walletClient
.
It hits await walletClient.requestAddresses()
. The function execution pauses here.
MetaMask (or the relevant wallet) prompts the user.
If the user approves, the Promise
returned by requestAddresses
resolves, await
gets the result (an array of addresses), and execution continues. The button text is updated, and success is logged.
If the user rejects or an error occurs, the catch
block executes, logging the error and resetting the button text.
Brave Shields: If using the Brave browser, ensure "Shields" are down for your development environment (e.g., localhost
or 127.0.0.1
) as they can sometimes interfere with wallet detection and interaction.
Asynchronicity is Crucial: Understanding async
/await
and Promises is fundamental in web3, as nearly all interactions with wallets and blockchains are asynchronous.
Error Handling: The try...catch
block is essential for a good user experience. Always handle potential errors, such as the user cancelling the connection request.
Viem Benefits: Using viem
provides type safety, better abstraction, and helper functions compared to directly using window.ethereum
, making development smoother, especially for more complex applications.
You have now successfully implemented a "Connect Wallet" button that uses modern JavaScript and the viem
library to interact with browser-based Ethereum wallets.
A practical guide to connecting your dApp: Adding a web3 wallet button with JavaScript and Viem. Learn to implement a 'Connect Wallet' feature using vanilla JavaScript, detect browser wallets, and leverage the `viem` library. Master asynchronous connection requests with `async`/`await` for robust dApp interactions.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
How to build full-stack web3 applications on ZKsync
JavaScript/TypeScript: viem, wagmi, synpress
Nodejs and pnpm
rindexer
Circle Compliance Engine and USDC
Fleek site hosting and CLI
How to build a static and dynamic React/Next.js site
How to leverage AI to code faster and more securely
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on June 10, 2025
Solidity Developer
Full-Stack Web3 Development Crash CourseDuration: 1h 12min
Duration: 1h 39min
Duration: 3h 08min
Duration: 1h 44min
Course Overview
About the course
How to build full-stack web3 applications on ZKsync
JavaScript/TypeScript: viem, wagmi, synpress
Nodejs and pnpm
rindexer
Circle Compliance Engine and USDC
Fleek site hosting and CLI
How to build a static and dynamic React/Next.js site
How to leverage AI to code faster and more securely
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on June 10, 2025