ethers.js: Human-Readable Contract ABIs

RicMoo
RicMoo
Published in
3 min readJun 7, 2018

--

We have recently added a new feature to the ethers.js Contract API, so we wanted to make a quick post to demonstrate using it.

The Application Binary Interface (ABI) for the Contract object now supports standard Solidity function signatures and event signatures, which makes the code much easier to read and understand.

// The ERC-20 ABI
var abi = [
"function balanceOf(address owner) view returns (uint)", "function transfer(address to, uint amount)", "event Transfer(address indexed from, address indexed to, uint amount)"];

The Contract object also fully supports ABIv2 signatures, so you can use tuples, dynamic arrays and any arbitrary level of nesting your heart desires.

// A simple version 2 ABI
var abi = [
"function addUser(tuple(string name, string email) user)", "function getUser(address addr) view returns (tuple(string name, string email) user)", "event UserAdded(address index addr, tuple(string name, string email) user)"];
// Or something unnecessarily complicated
var abi = [
"function sing(tuple(uint doe, tuple(address[2] ray, bytes me) far, string[] sew)[4] la, uint[][14][][][12] tea) payable returns (bool doe)"];

Example: ERC-20 Contract

We often receive requests for examples of using ERC-20 tokens with ethers.js, so we have put together a quick example using the new Solidity contract signature ABI format. Other than the ABI object, there would be absolutely no changes required to the rest of the code.

// The ERC-20 ABI
var abi = [
"function balanceOf(address owner) view returns (uint)",
"function transfer(address to, uint amount)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// Connect to the Ethereum mainnet
var provider = ethers.providers.getDefaultProvider('homestead');
// Load our wallet and connect to the provider
var wallet = new ethers.Wallet(privateKey, provider);
// Connect to "Pi Day N00b token" Contract (ERC-20 compliant)
var address = "0x334eec1482109bd802d9e72a447848de3bcc1063";
var contract = new ethers.Contract(address, abi, wallet);
// Listen for Transfer events (triggered after the transaction)
contract.ontransfer = function(from, to, amount) {
var text = ethers.utils.formatEther(amount);
console.log("Transfer");
console.log(" From: ", from);
console.log(" To: ", to);
console.log(" Amount: ", text);
// Transfer
// From: 0x59DEa134510ebce4a0c7146595dc8A61Eb9D0D79
// To: 0x851b9167B7cbf772D38eFaf89705b35022880A07
// Amount: 1.0
}
// Get the balance of the wallet before the transfer
contract.balanceOf(wallet.address).then(function(balance) {
var text = ethers.utils.formatEther(balance);
console.log("Balance Before:", text);
// Balance Before: 3.141592653589793238
})
// Transfer 1.0 token to another address (we have 18 decimals)
var targetAddress = "0x851b9167B7cbf772D38eFaf89705b35022880A07";
var amount = ethers.utils.parseUnits('1.0', 18);
contract.transfer(targetAddress, amount).then(function(tx) { // Show the pending transaction
console.log(tx);
// {
// hash: 0x820cc57bc7...0dbe181ba1,
// gasPrice: BigNumber("0x2540be400"),
// gasLimit: BigNumber("0x16e360"),
// value: BigNumber("0x0"),
// data: "0xa9059cbb" +
// "000000000000000000000000851b9167" +
// "b7cbf772d38efaf89705b35022880a07" +
// "00000000000000000000000000000000" +
// "00000000000000000de0b6b3a7640000",
// to: "0x334eec1482109Bd802D9e72A447848de3bCc1063",
// v: 37,
// r: "0x3fce72962a...a19b611de2",
// s: "0x16f9b70010...0b67a5d396",
// chainId: 1
// from: "0x59DEa134510ebce4a0c7146595dc8A61Eb9D0D79"
// }
// Wait until the transaction is mined...
return tx.wait();
}).then(function(tx) { console.log('Mined Transaction in block: ', tx.blockNumber); // Get the balance of the wallet after the transfer
contract.balanceOf(wallet.address).then(function(balance) {
var text = ethers.utils.formatUnits(balance, 18);
console.log("Balance After:", text);
// Balance After: 2.141592653589793238
})
});

This is kinda neat… How can I parse Solidity signatures myself?

If you wish to add support to your own scripts or utilities, you can parse a Solidity signature using the AbiCoder.parseSignature(signature) function to convert a Solidity signature to a standard ABI fragment.

// The signature to parse
var signature = "function foobar(uint256 baz) view returns (tuple(uint a, uint b) result)";
// Convert a signature (event or function) to standard ABI
AbiCoder.parseSignature(signature)
// {
// constant: true,
// inputs: [ { type: 'uint256', name: 'baz' } ],
// outputs: [ { type: 'tuple', name: 'result', components: [
// { type: 'uint', name: 'a' },
// { type: 'uint', name: 'b' }
// ] } ],
// payable: false,
// type: 'function',
// name: 'foobar',
// stateMutability: 'view'
// }
// Example: How many outputs does a function have?
function countOutputs(signature) {
return AbiCoder.parseSignature(signature).outputs.length;
}
// Example: What is the name of a function?
function functionName(signature) {
return AbiCoder.parseSignature(signature).name;
}

Thanks for reading! Hopefully you found this useful, and any feedback is always welcome. Please feel free to follow me on twitter to keep up with my latest random projects, articles and thoughts too.

Notice: Vyper support is on the roadmap, once it’s ready

--

--