July 2022, ethers.js 5.6, TypeScript. An example that prints USDT token total supply on Mainnet.
Metamask:
const ethereum = (window as any).ethereum;
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
const provider = new ethers.providers.Web3Provider(ethereum)
const walletAddress = accounts[0] // first account in MetaMask
const signer = provider.getSigner(walletAddress)
In case of RPC provider like Alchemy:
// Second parameter is chainId, 1 for Ethereum mainnet
const provider = new ethers.providers.JsonRpcProvider("API_URL", 1);
const signer = new ethers.Wallet("WALLET_PRIVATE_KEY", provider);
This part calls methods name, symbol, decimals, totalSupply.
const abi = [
"function name() public view returns (string)",
"function symbol() public view returns (string)",
"function decimals() public view returns (uint8)",
"function totalSupply() public view returns (uint256)",
"function approve(address _spender, uint256 _value) public returns (bool success)"]
const USDTContract = new ethers.Contract("0xdAC17F958D2ee523a2206206994597C13D831ec7", abi, signer)
const name = await USDTContract.name()
const symbol = await USDTContract.symbol()
const decimals = await USDTContract.decimals()
const totalSupply = await USDTContract.totalSupply()
console.log(`${symbol} (${name}) total supply is ${ethers.utils.formatUnits(totalSupply, decimals)}`)
When you call state-changing contract methods (for example, method "approve" with two parameters "SOME_ADDRESS" and "1000000")), in case of external RPC provider like Alchemy you'd need to set chainId
, nonce
, gasLimit
and gasPrice
yourself like in the example below:
const estimatedGasLimit = await USDTContract.estimateGas.approve("SOME_ADDRESS", "1000000"); // approves 1 USDT
const approveTxUnsigned = await USDTContract.populateTransaction.approve("SOME_ADDRESS", "1000000");
approveTxUnsigned.chainId = 1; // chainId 1 for Ethereum mainnet
approveTxUnsigned.gasLimit = estimatedGasLimit;
approveTxUnsigned.gasPrice = await provider.getGasPrice();
approveTxUnsigned.nonce = await provider.getTransactionCount(walletAddress);
const approveTxSigned = await signer.signTransaction(approveTxUnsigned);
const submittedTx = await provider.sendTransaction(approveTxSigned);
const approveReceipt = await submittedTx.wait();
if (approveReceipt.status === 0)
throw new Error("Approve transaction failed");
In case of call from MetaMask it would be just one line. Metamask sets all these fields.
await USDTContract.approve("SOME_ADDRESS", "1000000");
ethers.js has a Constants
object that contains common values. One of those is ethers.constants.HashZero
, which represents an empty bytes32
string.
There are more constants that you could read about here: https://docs.ethers.io/v5/api/utils/constants
For bytes
, you would place an empty array ([]
) as input in ethers.js. This is due to bytes
being an array-based value.
Best Answer
You need the input types as well to be able to decode it.
If plugging in the ABI is more convenient for you then you can use interface: