Use EthersJS To Connect To MetaMask & Read From + Write To A Contract
Why EthersJS?
One of the main reasons why to use Ethers.JS is because it aims to be the complete library that handles and interacts with the blockchain. It creates an abstraction and gives a bunch of other tools to make interacting with EVM-compatible chains easier.
Ethers.JS is still also a major library used with other even more absracted libraries like WAGMI.
I will be one to admit, the first time I saw the documentation from EthersJS (and I still feel this way), I felt overwhelmed. My hope with this article is to help give some more clarity to specific functions and how to use EthersJS. I also hope that the team behind EthersJS starts implementing more code examples.
What We’re Building
The good news is that we’re going to be building off my previous article’s code but implementing all the functions that were originally done with window.ethereum
with ethers.js
.
Function Equivalents
Taking a look from the previous article and the code, we’ll go through each of these functions.
Setting Up The Provider
The first thing we’ll one to do is establish a app-wide variable for our instantiated provider.
// Constants
// ========================================================
/**
* Main provider we'll be using throughout the application
*/
let ETHERSJS_PROVIDER = null;
// ...
// Initial Script Loaded On Window Loaded
// ========================================================
/**
* Init
*/
window.onload = async () => {
// ..
// Check if browser has wallet integration
if (typeof window?.ethereum !== "undefined") {
// BEFORE = window.ethereum
// Did not need to do this as window.ethereum was an Injected Provider
// AFTER = ethers.js
ETHERSJS_PROVIDER = new ethers.providers.Web3Provider(window.ethereum);
// ...
}
// ...
}
All Our Functions
We’re going to jump through each function and their equivalent in ethers.js in the context of the VanillaJS Wallet Connection code being refactored.
Connecting A Wallet
When you want to connect your wallet to the current site and add it to the the wallets list of Connected Sites.
// Constants
// ========================================================
/**
* To keep track of which wallet is connected throughout our app
*/
let WALLET_CONNECTED = '';
// ...
try {
// BEFORE - window.ethereum
// const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
// AFTER = ethers.js
const accounts = await ETHERSJS_PROVIDER.send("eth_requestAccounts");
WALLET_CONNECTED = accounts[0];
// ...
} catch (error) {
// ...
}
// ..
Retrieve Network Chain ID
Get the current network chain ID of the current connected wallet via its injected provider, which is window.ethereum
.
// ...
try {
// BEFORE - window.ethereum
// const chainId = await ethereum.request({ method: 'eth_chainId' });
// AFTER = ethers.js
// const chainId = await ETHERSJS_PROVIDER.send('eth_chainId');
// OR
const { chainId } = await ETHERSJS_PROVIDER.getNetwork();
onChainChanged(chainId);
// ...
} catch (error) {
// ...
}
// ..
Retrieve Wallet Permissions
If the wallet is already connected to the current site, check if there are already existing permissions that have been given from the current connected wallet.
// ...
// Check if browser has wallet integration
if (typeof window?.ethereum !== "undefined") {
// ...
// BEFORE - window.ethereum
// const hasWalletPermissions = await window.ethereum.request({ method: 'wallet_getPermissions' });
// AFTER = ethers.js
// Check if already connected with the number of permissions we have
const hasWalletPermissions = await ETHERSJS_PROVIDER.send('wallet_getPermissions');
console.log({ hasWalletPermissions });
// ...
}
// ...
Switch To Specific Network
Prompt the current connected wallet to switch to a specific chain id.
// Constants
// ========================================================
// ...
/**
* Required chain to interact with contract
*/
const CHAIN_ID_REQUIRED = 80001; //Mumbai
// ...
try {
// BEFORE - window.ethereum
// await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: `0x${CHAIN_ID_REQUIRED.toString(16)}` }], })
// AFTER = ethers.js
await ETHERSJS_PROVIDER.send('wallet_switchEthereumChain', [{ chainId: `0x${CHAIN_ID_REQUIRED.toString(16)}` }]);
// And you will have to get the current provider again
ETHERSJS_PROVIDER = new ethers.providers.Web3Provider(window.ethereum);
} catch (error) {
console.log({ error });
}
// ...
Contract RPC Read Request
Prompt the current wallet connected with the injected provider to perform a read request to a contract.
// ...
try {
// Setup Interface + Encode Function
const GetGreeting = CONTRACT_ABI.find(i => i.name === 'getGreeting');
const interface = new ethers.utils.Interface([GetGreeting]);
const encodedFunction = interface.encodeFunctionData(`${GetGreeting.name}`);
console.log({ encodedFunction });
// BEFORE - window.ethereum
// const result = await window.ethereum.request({
// method: 'eth_call', params: [{
// to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
// data: encodedFunction
// }]
// });
// AFTER = ethers.js
// const result = await ETHERSJS_PROVIDER.send(
// 'eth_call',
// [
// {
// to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
// data: encodedFunction
// }
// ]
// );
// OR
const result = await ETHERSJS_PROVIDER.call({
to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
data: encodedFunction
});
// ...
} catch (error) {
console.log({ error });
// ...
}
// ...
Contract RPC Write Request
Prompt the current wallet connected with the injected provider to perform a write request to a contract with a specific string payload.
// ...
try {
// Setup Interface + Encode Function
const SetGreeting = CONTRACT_ABI.find(i => i.name === 'setGreeting');
const interface = new ethers.utils.Interface([SetGreeting]);
const encodedFunction = interface.encodeFunctionData(`${SetGreeting.name}`, [greeting]);
console.log({ encodedFunction });
// BEFORE - window.ethereum
// const result = await window.ethereum.request({
// method: 'eth_sendTransaction',
// params: [{
// from: WALLET_CONNECTED,
// to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
// data: encodedFunction
// }]
// });
// AFTER = ethers.js
// const result = await ETHERSJS_PROVIDER.send(
// 'eth_sendTransaction',
// [{
// from: WALLET_CONNECTED,
// to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
// data: encodedFunction
// }]
// );
// OR
const signer = await ETHERSJS_PROVIDER.getSigner();
const result = await signer.sendTransaction({
to: CONTRACT_ON_CHAINS[CHAIN_CONNECTED.id],
data: encodedFunction
});
// ...
} catch (error) {
console.log({ error });
// ...
}
// ...
Event On Network Chain ID Changed
An event hook that is trigged when the current wallet connected changes their network id within their wallet.
// ...
// Check if browser has wallet integration
if (typeof window?.ethereum !== "undefined") {
// ...
// BEFORE - window.ethereum
// window.ethereum.on('chainChanged', onChainChanged);
// AFTER = ethers.js
ETHERSJS_PROVIDER.provider.on('accountsChanged', onAccountsChanged);
// ...
}
// ...
Event On Wallet Account Changed
An event hook that is trigged when the current wallet connected changes to another wallet address that is going to be or is already connected.
// ...
// Check if browser has wallet integration
if (typeof window?.ethereum !== "undefined") {
// ...
// BEFORE - window.ethereum
// window.ethereum.on('accountsChanged', onAccountsChanged);
// AFTER = ethers.js
ETHERSJS_PROVIDER.provider.on('accountsChanged', onAccountsChanged);
// ...
}
// ...
Demo
Here is a demo of the application working.
NOTE: You’ll need MetaMask installed with your Chrome browser.
Full Code
If you want to see the full source code, check out the following repository.
Where To Go From Here?
Want to dive deeper into more RPC requests, with different networks, and even try it with a local network? Take a look at this comprehensive repository that has more wallet interactions:
I should be writing more articles on how a frontend can interact with a wallet and different EVM chains, so make sure to follow my account.
If you got value from this, please give it some love by sharing it, and please also follow me on twitter (where I’m quite active) @codingwithmanny and instagram at @codingwithmanny.