How to interact with a smart contract using Ethers JS and JavaScript

In this tutorial, we are going to learn how to interact with a smart contract using Ethers JS and JavaScript. We are going to see how to read data from a smart contract and send transactions to it.

In this tutorial, we are going to learn how to interact with a smart contract using Ethers JS and JavaScript. We are going to see how to read data from a smart contract and send transactions to it.

Using Ethers JS, you can interact with a smart contract by creating a Contract object and then calling the functions of the smart contract.

Here is the full code example:

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE);

const signer = provider.getSigner()

// The smart contract address
const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"

// The smart contract ABI
const ABI = [
    {
        "constant":true,
        "inputs":[{"name":"who","type":"address"}],
        "name":"balanceOf",
        "outputs":[{"name":"","type":"uint256"}],
        "payable":false,
        "stateMutability":"view",
        "type":"function"
    },
    {
        "constant":false,
        "inputs":[
            {"name":"_to","type":"address"},
            {"name":"_value","type":"uint256"}
        ],
        "name":"transfer",
        "outputs":[],
        "payable":false,
        "stateMutability":"nonpayable",
        "type":"function"
    }
]

// Create a contract instance
const contract = new ethers.Contract(contractAddress, ABI, signer);


// Read the state (it can be any value from the "Read Contract" section in Etherscan, any function that has "stateMutability":"view" or "pure" in the ABI)
const myAddress = "0x5b8f1310A956ee1521A7bB56160451C786289aa9"
const myBalance = await contract.balanceOf(myAddress)


// Call a function that mutates the state (which sends a transaction and costs gas)
// Works the same as above but this time it returns a transaction object
// To be able to call this, the provider needs to have a signer (a wallet from which it sends the transactions)

const to = "0x5b8f1310A956ee1521A7bB56160451C786289aa9"

// USDT has 6 decimals of precision so I need to change the units of the number so it has 6 decimals of precision
// Other tokens have 18 decimals (which corresponds to wei) so you would use ethers.utils.parseEther() instead
// Other arguments don't necessarily need that conversion
const amount = ethers.utils.parseUnits("200", 6) // send 200 tokens

const transferTx = await contract.transfer(to, amount)

From there, you can just copy the ABI for the functions you need:

From there, you can just copy the ABI for the functions you need:

Now let's talk about this code in details and explain everything.

Creating a Contract instance to interact with the smart contract

To interact with a contract you need its address on the blockchain and an ABI.

An ABI is an interface that describes the functions that a smart contract has and how they work, what they return and what they take in parameters.

You don't need to define all the functions of a smart contract in the ABI, just the ones you are going to use.

As example, we use the USDT smart contract that you can find here:

https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7#code

If you go to the "Contract" tab in the "Code" section and scroll down a bit, you can see the full ABI:

From there, you can just copy the ABI for the functions you need:

Once you have that in your code, along with the contract address, you can create a Contract object by passing it the address and the ABI you just created.

Here is an example with the USDT smart contract:

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE);

const signer = provider.getSigner()

// The smart contract address
const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"

// The smart contract ABI
const ABI = [
    {
        "constant":true,
        "inputs":[{"name":"who","type":"address"}],
        "name":"balanceOf",
        "outputs":[{"name":"","type":"uint256"}],
        "payable":false,
        "stateMutability":"view",
        "type":"function"
    },
    {
        "constant":false,
        "inputs":[
            {"name":"_to","type":"address"},
            {"name":"_value","type":"uint256"}
        ],
        "name":"transfer",
        "outputs":[],
        "payable":false,
        "stateMutability":"nonpayable",
        "type":"function"
    }
]

// Create a contract instance
const contract = new ethers.Contract(contractAddress, ABI, signer);

How to call smart contract functions using Ethers JS

Once you have your Contract instance, you can call the functions of the smart contract that you defined in your ABI.

There are 2 types of functions in Solidity. The ones that mutate the state (the variables of the contract) and the ones that don't.

Calling a function that just reads the state won't create a transaction but calling a function that mutates the state will create a transaction so it costs gas.

To call a smart contract function, you just use the Contract instance you created and call the function you want (as long as it's in the ABI) as if it was a method of that instance.

It's the same for functions that read from and write to the state.

If you want to send parameters to the contract function, you just pass them when calling the function on the Contract instance.

Calling a function that reads the state

For example, if I want to call the balanceOf function of the USDT smart contract, which it takes in parameter the address you want to check the balance of, I can do it that way:

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE);

// The smart contract address
const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"

// The smart contract ABI
const ABI = [
    {
        "constant":true,
        "inputs":[{"name":"who","type":"address"}],
        "name":"balanceOf",
        "outputs":[{"name":"","type":"uint256"}],
        "payable":false,
        "stateMutability":"view",
        "type":"function"
    },
]

// Create a contract instance
const contract = new ethers.Contract(contractAddress, ABI, provider);

const myAddress = "0x5b8f1310A956ee1521A7bB56160451C786289aa9"
const myBalance = await contract.balanceOf(myAddress)

If the contract function you call returns something, it will be returned by Ethers when you call that function.

As you can see, here we don't need a Signer because we don't call a function that mutates the state, so we can just use our provider.

If calling that function fails (for example if the smart contract reverts the call), an error will be thrown and the error object will have these properties:

  • address - the contract address
  • args - the arguments passed to the method
  • transaction - the transaction

Calling a function that mutates the state

It's the exact same as calling a function that reads the state. Unless this time, calling these function create a transaction because they require you to pay gas fees.

So to send them, your provider needs to have a Signer.

When you create the Contract instance, the last parameter is a provider or a Signer. If you want to call functions that mutate the state, you need to pass a Signer to send the transactions from.

If the provider is a wallet connected to your website, you just need to call provider.getSigner().

A Wallet instance is also a valid Signer.

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE);

// The smart contract address
const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"

// The smart contract ABI
const ABI = [
    {
        "constant":false,
        "inputs":[
            {"name":"_to","type":"address"},
            {"name":"_value","type":"uint256"}
        ],
        "name":"transfer",
        "outputs":[],
        "payable":false,
        "stateMutability":"nonpayable",
        "type":"function"
    },
]

// Create a contract instance
const signer = provider.getSigner()
const contract = new ethers.Contract(contractAddress, ABI, signer);


const to = "0x5b8f1310A956ee1521A7bB56160451C786289aa9"

// USDT has 6 decimals of precision so I need to change the units of the amount to transfer
// Other tokens have 18 decimals
const amount = ethers.utils.parseUnits("200", 6) // send 200 tokens

const transferTx = await contract.transfer(to, amount)

As you can see, it works just like calling a function that doesn't mutate the state.

The only difference is that it returns a TransactionResponse object. To learn more about it, check out the official documentation here.

You can also get more information about the transaction response object in our guide about how to get a transaction state (it's in the first part where we talk about the getTransaction function):

How to get the state of a transaction using Ethers JS and JavaScript
In this tutorial, we are going to learn how to get the state of a transaction using Ethers JS and JavaScript.

How to send Ethereum when calling a smart contract function

Sometimes, you need to send Ethereum when calling a smart contract function. The most common example is when you mint NFTs, you send Ethereum to purchase the NFT.

If you want to send Ethereum when calling a function, you can add an object in the last parameter of the function that you're calling which contains transaction options and in these options you can put the amount of Ethereum to send.

Here is an example:

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE)

const signer = provider.getSigner()
const myNftContract = new ethers.Contract(contractAddress, ABI, signer)

// Send 0.1 ETH when calling the mint function of the smart contract
myNftContract.mint(1, { value: ethers.utils.parseEther("0.1") })

In the transaction options object, you can pass all the options available here like the gas amount, the gas price to use, the value and all the others.

Human-readable ABI

Another thing to know is that you can also define the ABI in a more readable way if you know a little bit of Solidity.

Instead of an object, to define a function in the ABI, you can pass a string with a Solidity-like definition of the function:

const ABI = [
    "function balanceOf(address _who) view returns (uint256)",
    "function transfer(address _to, uint256 _value)"
]

It's basically like the Solidity definition of a function but without all the keywords like the visibility, the override and all the others.

And that's it 🎉

Thank you for reading this article