=============================================================================================================================================> I OFFER FULL STACK BLOCKCHAIN DEVELOPMENT SERVICES: ERC20/721 TOKEN CREATION, TOKEN SALE SMART CONTRACT, WHITELISTING, LENDING PROTOCOL, MARKETING REFERRAL, DAO DEVELOPMENT AND MORE.
FRONT END INTEGRATION INCLUDED.
IF YOU NEED SMART CONTRACTS, CONTACT ME.
IF YOU NEED FRONT END INTEGRATION WITH SMART CONTRACTS, CONTACT ME.
SEE MY OFFERS, CLIENT TESTIMONIALS AND PREVIOUS WORK BY VISITING =>https://www.muratcanyuksel.xyz
OR SEND ME AN EMAIL VIA mail@muratcanyuksel.xyz
=============================================================================================================================================>
Hello everyone! In this post, I'll show you how to connect to different web3 wallets using Wagmi.sh. In the course of this tutorial, we'll write a basic smart contract using Solidity, create and deploy the project using Hardhat, interact with it using EthersJS on top of React, deploy the frontend to GhPages and hide our sensitive data with env variables.
So, first off, let me tell you about what is Wagmi.sh, what we'll use it for, and why would we need it anyway?
Introduction to Wagmi.sh and the project we're going to build
According to their website (https://wagmi.sh/
) "wagmi is a collection of React Hooks containing everything you need to start working with Ethereum." What interests me initially is that we can connect to different web3 wallet providers easily using wagmi. Although there are other libraries that does the same thing, I've found wagmi to be really easy to use. They also have a library comparison page where they outline their pros and cons (https://wagmi.sh/docs/comparison
). The biggest pro for me was that using wagmi with Ethers.js is a breeze. Wagmi supports the following wallet providers: Metamask, Coinbase Wallet, WalletConnect, and Injected.
Now, the project we're going to be a fairly simple one. When the app opens the first time, we will be presented with different buttons signifying their respective wallet providers. We can choose any one of them to connect to the app. Here's the first screen we see.
Once connected, we'll see an input field and a button that says "Wave". If we enter a name and hit the "Wave" button, the wallet provider will be triggered and we'll be asked to pay for the transaction. After the transaction is complete, the name we've entered will be displayed on the screen.
At any time we can disconnect from the chosen wallet provider and connect with another one.
We will use the Goerli test network for this application.
Part One: Creating the backend
Writing and Deploying the Smart contract with Hardhat
Let's start our application by creating an empty Hardhat project. We'll use Hardhat to deploy our smart contract into the Goerli test network. We'll use the solidity language to write our smart contract.
To start, let's create an empty folder. We'll keep both our smart contract and frontend in this folder for ease of use. Since I use Linux, I'll create a folder named wagmi-project
by typing mkdir wagmi-project
in the terminal. I'll cd
into the folder and run the following command on the terminal to create a package.json file: yarn init -y
(if you use npm, you'd enter npm init -y
instead). Once that's done, I'll install hardhat with entering yarn add --dev hardhat
in the terminal. Then, I'll start a new hardhat project by entering npx hardhat
in the terminal and choose to create a new Javascript project. Then I'll add some dependencies with yarn add --dev @nomiclabs/hardhat-waffle@^2.0.0 ethereum-waffle@^3.0.0 chai@^4.2.0 @nomiclabs/hardhat-ethers@^2.0.0 ethers@^5.0.0
. That's it for the hardhat setup. Now I'll just delete the folders inside contracts, scripts and test folders that we've just created.
Then I'll enter yarn add dotenv
as I'll use environment variables later on to protect sensitive data such as my private wallet key.
After all that's done, I go to my hardhat.config.js
file and change it's contents to the following:
require("@nomiclabs/hardhat-waffle");
require("dotenv").config();
module.exports = {
solidity: "0.8.0",
networks: {
goerli: {
url: `${process.env.ALCHEMY_GOERLI_URL}`,
accounts: [`${process.env.MY_PRIVATE_KEY}`],
},
},
};
As you can see, there are some things going on here. Apart from requiring waffle from hardhat and dotenv library for env variables, I also specify the Solidity compiler version, the network I'm going to use (goerli test network in our case), and pass my sensitive data that's been cached from unwanted attention by env variables. I'll show how to find, add, and hide those env variables in the next step.
Adding env variables to hide sensitive data
Alchemy API
To start with, I'll create an empty .env
file. I will place this file in the wagmi-frontend
folder I'll create soon. You can just create is somewhere else right now and move it to that forlder later on. Now to populate it, I'll need some external help from Alchemy. If you don't have an Alchemy account, please go to https://www.alchemy.com/
and create one (it's free). Now, since we have our Alchemy account, we'll go to our dashboard and create a new project by clicking the +create app
button on the top right. While creating the app, it is of utmost importance to choose Goerli
for our network, because we're not going to use the Ethereum Mainnet as we don't want to spend real money on practice. Now that we've created our application on Alchemy, we'll click the view key
button on the right of the project's tab and copy-paste the API KEY
and HTTPS
sections. We will need both of them for our project.
Metamask private key
Now, for this step you need a metamask account with some fake money for Goerli test network in it. To continue with the tutorial, please install the Metamask extension on your browser, request some test money from a faucet like this one https://goerlifaucet.com/
(you can login and request money using your Alchemy account since you have one now)
NB: Please use a burner wallet. That is to say, never put real money in this wallet, never use it for production or any other purpose than practice. It is better just to forget about and never use this particular wallet after the tutorial.
Now, to get the private key, click on the three vertical dots on the right side of the metamask extension and choose account details
. there, you'll see a button that'll direct you to your private key. Enter your password there and get the private key. Once you copy your private key, let's go to our .env file and paste all the info we've got so far there. It should look something like the following:
ALCHEMY_GOERLI_URL=https://eth-goerli.g.alchemy.com/somethingsomething
MY_PRIVATE_KEY= somethingsomethingmyprivatekey
ALCHEMY_ID=sthsthmyalchemyID
Don't show the info in your .env file to anyone, and if you're going to use GitHub for your project, please check your .gitignore file includes .env files so that you won't post these vital info to the public. People even steal fake money, I've heard it.
With the .env file populated, we can now write our smart contract for deployment.
Writing the Smart Contract
Now that we've set up our project structure, we need a smart contract to deploy. Let's create a Wave.sol file in the contracts folder we've created by initiating a hardhat project and paste the following contract inside of it:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Wave {
string public name = "Murat";
function setName(string memory _name) external {
name = _name;
}
}
What's going on here? First of all, .sol
extension indicated that this is a Solidity file, i.e. the code written inside of this file is with Solidity language. In the first line I specify what kind of license I'm using (MIT in this case, open source), and the compiler version for Solidity. In this case, it means anything above 0.8.0 passes.
I start my contract with the contract
keyword and give it a name of Wave
. Inside of it I define a public string and give it an initial value of Murat, my name. You can give whatever you want obviously. Then, I write a function called setName
and give it a parameter of type string
which is saved in the memory. Then, I make it external so that it can be called from outside of the contract. All this function does is change the value of the public string taking the value that's put in the function parameter. Notice that the function parameter starts with an underline _name
. This is not necessary, but is a convention used in the Solidity community. It helps us to discern between what is an actual variable and what's a parameter per se.
Do not worry too much about the Solidity code for now, this tutorial doesn't focus on writing Solidity smart contracts.
Now we're finished with the smart contract, let's tie it all together and deploy it to the test network!
Deploying the Smart Contract
In order for these steps to work, you should be using a supported version of nodeJS for hardhat. It is generally the LTS version.
Go to the root of the project, and enter npx hardhat compile
in the terminal. If everything goes alright, you'll be prompted with Compiled 1 Solidity file successfully
, if not, enter npx hardhat clean
and redeploy until it's succesfull.
Now if you check your folder structre again, you'll see there's a new folder called artifacts
. If you go to the contracts
folder inside of that artifacts
folder, you'll see a .json file with the name you've gave to your smart contract. We will use this file later on, it is our contract abi (application binary interface).
Now, let's go back and cd into our scripts
folder and add a deploy.js
file with the following code inside of it:
const hre = require("hardhat");
async function main() {
// We get the contract to deploy
const [owner] = await hre.ethers.getSigners();
//Here make sure you enter the name of the .sol file you've created in the contracts folder.
const WaveContractFactory = await hre.ethers.getContractFactory("Wave");
const WaveContract = await WaveContractFactory.deploy();
await WaveContract.deployed();
console.log("WaveContract deployed to:", WaveContract.address);
console.log("WaveContract owner address:", owner.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
The above code will make sure our code is deployed. As I've mentioned in the comments, just make sure you've entered the name of the smart contract you've written previously.
Now, we write the following into the terminal npx hardhat run scripts/deploy.js --network goerli
If succcesful, you'll be prompted with something like:
WaveContract deployed to: somehweresomething
WaveContract owner address: somehweresomethingelse
Save these addresses in somewhere safe and hidden, we will use them later on.
Congrats! You've just deployed a smart contract to the blockchain! You can even check your contract by going to https://goerli.etherscan.io/
and pasting the address that this contract was deployed!
Even for a simple contract, that was a lot of moving parts, so pat yourself on the back. Now, we'll get into the frontend stuff, i.e. how are we going to make the users interact with the contract we've just deployed.
Part Two: Creating the Frontend
Installing the dependencies
I'll use reactJS in this project, so let's go to our root folder and create a react project by entering npx create-react-app wagmi-frontend
in the terminal.
After that's done our folder structure should be something like the following:
-Wagmi-project (root)
--artifacts
--cache
-contracts
-node_modules
-scripts
-test
-wagmi-frontend
-.gitignore
-hardhat-config.js
-package.json
-README.md
-yarn.lock
Now, we'll cd into this new wagmi-frontend
folder and install ethersJS as well as wagmi.sh. To install ethersJS library, I write the following command into the terminal yarn add ethers
, and to install wagmi.sh I write the following yarn add wagmi ethers
.
I also install buffer
since node polyfills are not bundled with webpack anymore, to install this package is the easiest solution I've found to work with the error we'd see if we didn't have buffer. To install buffer, I write the following to command line yarn add buffer
. Then, I'll go to my index.js
file inside wagmi-frontend/src/
and require buffer as such window.Buffer = require("buffer/").Buffer;
at the top of the component. Now my index.js
file looks like this:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
window.Buffer = require("buffer/").Buffer;
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Lastly, I'll install gh-pages as I want to deploy the project to Github Pages. To do that, I write the following command into the terminal yarn add gh-pages
.
Now we're done with installing the dependencies, we can see how to use them to create a frontend to interact with our smart contract.
Writing the frontend
Since we're inside of our wagmi-frontend
folder, our folder structure (excluding anything that's other than wagmi-frontend
) should look like the following:
-node_modules
-public
-src
-.env
-.gitignore
-hardhat-config.js
-package.json
-README.md
-yarn.lock
Now I'll add a couple of more folders and files into this wagmi-frontend
folder. I'll add 2 folders named contracts
and style
, I'll also add a Profile.js
file. Before proceeding any further, I'll create a Wave.json
file in the contracts
folder I've just created. To populate this file, I need to go back to my root folder (not the wagmi-frontend
folder we're currently in, but the folder that contains it also). There, you'll remember we had an artifacts
folder, I'll cd into it and find the contracts
folder. Inside of the contracts
folder, there's this Wave.json
file that's been created by hardhat. I'll copy everthing inside of it.
After copying the contents of that Wave.json
file, I'll go back to the wagmi-frontend
folder, go inside src
, then to contracts
folder there and create another Wave.json
file. I'll paste everything I've copied inside of this file. This file contains the abi of our smart contract.
In the style
folder, I'll create a profile.css
file and add the following contents inside of it (don't worry about this at all, it's just to make the buttons look a bit nicer. This is not a post about writing CSS after all.):
.btn {
display: flex;
flex-wrap: wrap;
width: 150px;
margin-bottom: 10px;
padding: 5px;
}
I'll also go into the App.css
file in the src
folder and add the following contents:
.App {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: black;
color: white;
height: 100vh;
}
Now I can go into App.js
file, delete everything inside of it and write my own code.
App.js
I'll start by importing the dependencies for the application:
import { useState } from "react";
import { ethers, utils } from "ethers";
import abi from "./contracts/Wave.json";
import Profile from "./Profile";
import "./App.css";
import {
WagmiConfig,
createClient,
defaultChains,
configureChains,
} from "wagmi";
import { alchemyProvider } from "wagmi/providers/alchemy";
import { publicProvider } from "wagmi/providers/public";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";
import { InjectedConnector } from "wagmi/connectors/injected";
import { MetaMaskConnector } from "wagmi/connectors/metaMask";
import { WalletConnectConnector } from "wagmi/connectors/walletConnect";
You see that I'll use useState
hook from React, ethers
library, the contract abi we've saved in Wave.json
file, a Profile
component we'll create later on, App.css
file, dependencies for wagmi which includes providers alongside with wallet connectors. After that's done, I'll add the following lines of code just under it:
const alchemyId = process.env.ALCHEMY_ID;
// Configure chains & providers with the Alchemy provider.
// Two popular providers are Alchemy (alchemy.com) and Infura (infura.io)
const { chains, provider, webSocketProvider } = configureChains(defaultChains, [
alchemyProvider({ alchemyId }),
publicProvider(),
]);
// Set up client
const client = createClient({
autoConnect: true,
connectors: [
new MetaMaskConnector({ chains }),
new CoinbaseWalletConnector({
chains,
options: {
appName: "wagmi",
},
}),
new WalletConnectConnector({
chains,
options: {
qrcode: true,
},
}),
new InjectedConnector({
chains,
options: {
name: "Injected",
shimDisconnect: true,
},
}),
],
provider,
webSocketProvider,
});
These parts can also be found on the wagmi documentation. What we're doing here is asically, we take our Alchemy Id we've saved in our .env file and then set up the client for different wallet options. You see that we have Metamask, Coinbase, WalletConnector and Injected here. After we specified these parts, we can start writing our main function, so let's do that.
const App = () => {
const [isWalletConnected, setIsWalletConnected] = useState(false);
const [userName, setUserName] = useState("");
const [displayedUserName, setDisplayedUserName] = useState("initial name");
const [error, setError] = useState(null);
const contractAddress = "theaddressthatourcontracthasbeendeployedtocomeshere";
const contractABI = abi.abi;
}
As you can see, I have some constants for changing the state, the contract address we've got when we deployed our contract to the (test) blockchain, and the contract abi that we've imported from our Wave.json
file.
Next, I'll add the necessary functions. Let's look at what they're doing:
const handleInput = (name) => {
setUserName(name.target.value);
};
const handleChild = (status) => {
console.log(status);
status ? setIsWalletConnected(true) : setIsWalletConnected(false);
};
const handleClick = async (e) => {
e.preventDefault();
if (window.ethereum) {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const waveContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const txn = await waveContract.setName(
utils.formatBytes32String(userName)
);
console.log("setting user name");
await txn.wait();
console.log("user name set", txn.hash);
setDisplayedUserName(userName);
}
};
handleInput
function is easy to understand. We'll take the user's input and set it to the state and display it in the UI.
handleChild
function is a bit more complicated as it is linked to the child component (Profile.js
) we'll create in the next steps. It'll make more sense retrospectively, but the gist is that I'll send this function to the child as props, check if any wallet is connected there, and send the boolean data from child back to this function (which is in the parent component). That's what I do in the ternary operator (status ? setIsWalletConnected(true) : setIsWalletConnected(false);
). Actually, I don't even need to write this ternary operator, I can just do away with setIsWalletConnected(true)
too but I want to be as explicit as possible so that I wouldn't look at the screen for hours and hours when I want to go back to this piece of code after some time.
Next, handleClick
function. It is asynchronous, and it checks if there's a wallet in the browser, then by using ethersJS library, it deals with providers and signers. txn
is the transaction, it converts the userName to Bytes32 so that EVM(Ethereum Virtual Machine) can understand it. Then it waits for the transaction to be mined and then it sets the userName.
Now let's look at the last bits of our App.js component:
let userInteraction;
if (isWalletConnected === true) {
userInteraction = (
<div>
<input onChange={handleInput} type="text" />
<button onClick={handleClick}>Wave</button>
<p> {displayedUserName} waved!</p>
</div>
);
}
return (
<div className="App">
<h1>Wagmi Project</h1>
{userInteraction}
<WagmiConfig client={client}>
<Profile handleChild={handleChild} />
</WagmiConfig>
</div>
);
};
export default App;
First, I define an empty userInteraction
variable. Then depending on the connection status of the wallet, I populate it with the appropriate UI. If the wallet is connected, I'll display the input field and the button. If the wallet is not connected, they won't be there.
In our return statement, I pass the above conditional statement, and invoke our Profile.js
child component with handleChild
passed as props as I've mentioned earlier.
Just for making sure everything is in place, let's see the whole App.js
component:
import { useState, useEffect } from "react";
import { ethers, utils } from "ethers";
import abi from "./contracts/Wave.json";
import Profile from "./Profile";
import "./App.css";
import {
WagmiConfig,
createClient,
defaultChains,
configureChains,
} from "wagmi";
import { alchemyProvider } from "wagmi/providers/alchemy";
import { publicProvider } from "wagmi/providers/public";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";
import { InjectedConnector } from "wagmi/connectors/injected";
import { MetaMaskConnector } from "wagmi/connectors/metaMask";
import { WalletConnectConnector } from "wagmi/connectors/walletConnect";
const alchemyId = process.env.ALCHEMY_ID;
// Configure chains & providers with the Alchemy provider.
// Two popular providers are Alchemy (alchemy.com) and Infura (infura.io)
const { chains, provider, webSocketProvider } = configureChains(defaultChains, [
alchemyProvider({ alchemyId }),
publicProvider(),
]);
// Set up client
const client = createClient({
autoConnect: true,
connectors: [
new MetaMaskConnector({ chains }),
new CoinbaseWalletConnector({
chains,
options: {
appName: "wagmi",
},
}),
new WalletConnectConnector({
chains,
options: {
qrcode: true,
},
}),
new InjectedConnector({
chains,
options: {
name: "Injected",
shimDisconnect: true,
},
}),
],
provider,
webSocketProvider,
});
const App = () => {
const [isWalletConnected, setIsWalletConnected] = useState(false);
const [userName, setUserName] = useState("");
const [displayedUserName, setDisplayedUserName] = useState("initial name");
const [error, setError] = useState(null);
const contractAddress = "0x17c5A76a5D6db7740821425aFa029B3494DeecaB";
const contractABI = abi.abi;
const handleInput = (name) => {
setUserName(name.target.value);
};
const handleChild = (status) => {
console.log(status);
status ? setIsWalletConnected(true) : setIsWalletConnected(false);
};
const handleClick = async (e) => {
e.preventDefault();
if (window.ethereum) {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const waveContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const txn = await waveContract.setName(
utils.formatBytes32String(userName)
);
console.log("setting user name");
await txn.wait();
console.log("user name set", txn.hash);
setDisplayedUserName(userName);
}
};
let userInteraction;
if (isWalletConnected === true) {
userInteraction = (
<div>
<input onChange={handleInput} type="text" />
<button onClick={handleClick}>Wave</button>
<p> {displayedUserName} waved!</p>
</div>
);
}
return (
<div className="App">
<h1>Wagmi Project</h1>
{userInteraction}
<WagmiConfig client={client}>
<Profile handleChild={handleChild} />
</WagmiConfig>
</div>
);
};
export default App;
We're done with the App.js
component. I know it is a lot, but we're left one more component, the child, Profile.js
. After that we're all done.
Writing the child component
I start by importing whatever needs to be imported at the beginning as such:
import { React, useEffect } from "react";
import {
useAccount,
useConnect,
useDisconnect,
useEnsAvatar,
useEnsName,
} from "wagmi";
import "./style/profile.css";
Next comes our child component's main function with handleChild
that's been passed from the parent as props:
const Profile = ({ handleChild }) => {
const { address, connector, isConnected } = useAccount();
const { data: ensAvatar } = useEnsAvatar({ addressOrName: address });
const { data: ensName } = useEnsName({ address });
const { connect, connectors, error, isLoading, pendingConnector } =
useConnect();
const { disconnect } = useDisconnect();
useEffect(() => {
console.log(isConnected);
if (isConnected === true) {
handleChild(true);
}
});
You'll notice that I use destructured props with curly braces around them, I find this to be more readable.
Here we have our constants that can be found in wagmi.sh documentation that helps us to use its hooks.
In the useEffect
hook, I check if the user is connected. If they are, I send the data to parent by invoking the handleChild
function. Remember, it does set the state of the wallet as connected in the parent component. Next, I have a conditional statement that checks if the user is connected. f they are, I display the address of the wallet that's connected, and the name of it (Metamask or Coinbase for instance):
if (isConnected) {
return (
<div>
{/* <img src={ensAvatar} alt="ENS Avatar" /> */}
<div>{ensName ? `${ensName} (${address})` : address}</div>
<div>Connected to {connector.name}</div>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
To wrap it up, I have my return statement. It's pretty self-explanatory:
return (
<div>
{connectors.map((connector) => (
<button
className="btn"
disabled={!connector.ready}
key={connector.id}
onClick={() => connect({ connector })}
>
{connector.name}
{!connector.ready && " (unsupported)"}
{isLoading &&
connector.id === pendingConnector?.id &&
" (connecting)"}
</button>
))}
{error && <div>{error.message}</div>}
</div>
);
};
export default Profile;
That's all, actually. Now, if we went to our wagmi-frontend
folder and enter yarn start
(or npm run start
if you're using npm), we can see that the app is running.
Closing thoughts
Congratulations! You've finished the tutorial. You can find the source code for this tutorial here=> https://github.com/muratcan-yuksel/wagmi-post . I know there were a lot of moving parts and things to do but I hope you've enjoyed it. If you have any questions, feel free to reach out to me on Twitter or Github.
Keep calm & happy hacking!
Top comments (0)