There are several formats available to specify an ABI for a Smart Contract, which specifies to the under-lying library what methods, events and errors exist so that encoding and decoding the data from and to the network can be handled by the library.
The supports ABI types are:
The Human-Readable ABI was introduced early by ethers, which allows for a Solidity signatures to be used to describe each method, event and error.
It is important to note that a Solidity signature fully describes all the properties the ABI requires:
- name
- type (constructor, event, function)
- inputs (types, nested structures and optionally names)
- outputs (types nested structures and optionally names)
- state mutability (for constructors and methods)
- payability (for constructors and methods)
- whether inputs are indexed (for events)
This allows for a simple format which is both machine-readable (since the parser is a machine) and human-readable (at least developer-readable), as well as simple for humans to type and inline into code, which improves code readability. The Human-Readable ABI is also considerably smaller, which helps reduce code size.
A Human-Readable ABI is simple an array of strings, where each string is the Solidity signature.
Signatures may be minimally specified (i.e. names of inputs and outputs may be omitted) or fully specified (i.e. with all property names) and whitespace is ignored.
Several modifiers available in Solidity are dropped internally, as they are not required for the ABI and used old by Solidity's semantic checking system, such as input parameter data location like "calldata"
and "memory"
. As such, they can be safely dropped in the ABI as well.
Human-Readable ABI Example
const humanReadableAbi = [
"constructor(string symbol, string name)",
"function transferFrom(address from, address to, uint value)",
"function balanceOf(address owner) view returns (uint balance)",
"event Transfer(address indexed from, address indexed to, address value)",
"error InsufficientBalance(account owner, uint balance)",
"function addPerson(tuple(string name, uint16 age) person)",
"function addPeople(tuple(string name, uint16 age)[] person)",
"function getPerson(uint id) view returns (tuple(string name, uint16 age))",
"event PersonAdded(uint indexed id, tuple(string name, uint16 age) person)"
];
The Solidity JSON ABI is a standard format that many tools export, including the Solidity compiler. For the full specification, see the Solidity compiler documentation.
Various versions include slightly different keys and values. For example, early compilers included only a boolean "constant"
to indicate mutability, while newer versions include a string "mutabilityState"
, which encompasses several older properties.
When creating an instance of a Fragment using a JSON ABI, it will automatically infer all legacy properties for new-age ABIs and for legacy ABIs will infer the new-age properties. All properties will be populated, so it will match the equivalent created using a Human-Readable ABI fragment.
const jsonAbi = `[
{
"type": "constructor",
"payable": false,
"inputs": [
{ "type": "string", "name": "symbol" },
{ "type": "string", "name": "name" }
]
},
{
"type": "function",
"name": "transferFrom",
"constant": false,
"payable": false,
"inputs": [
{ "type": "address", "name": "from" },
{ "type": "address", "name": "to" },
{ "type": "uint256", "name": "value" }
],
"outputs": [ ]
},
{
"type": "function",
"name": "balanceOf",
"constant":true,
"stateMutability": "view",
"payable":false, "inputs": [
{ "type": "address", "name": "owner"}
],
"outputs": [
{ "type": "uint256"}
]
},
{
"type": "event",
"anonymous": false,
"name": "Transfer",
"inputs": [
{ "type": "address", "name": "from", "indexed":true},
{ "type": "address", "name": "to", "indexed":true},
{ "type": "address", "name": "value"}
]
},
{
"type": "error",
"name": "InsufficientBalance",
"inputs": [
{ "type": "account", "name": "owner"},
{ "type": "uint256", "name": "balance"}
]
},
{
"type": "function",
"name": "addPerson",
"constant": false,
"payable": false,
"inputs": [
{
"type": "tuple",
"name": "person",
"components": [
{ "type": "string", "name": "name" },
{ "type": "uint16", "name": "age" }
]
}
],
"outputs": []
},
{
"type": "function",
"name": "addPeople",
"constant": false,
"payable": false,
"inputs": [
{
"type": "tuple[]",
"name": "person",
"components": [
{ "type": "string", "name": "name" },
{ "type": "uint16", "name": "age" }
]
}
],
"outputs": []
},
{
"type": "function",
"name": "getPerson",
"constant": true,
"stateMutability": "view",
"payable": false,
"inputs": [
{ "type": "uint256", "name": "id" }
],
"outputs": [
{
"type": "tuple",
"components": [
{ "type": "string", "name": "name" },
{ "type": "uint16", "name": "age" }
]
}
]
},
{
"type": "event",
"anonymous": false,
"name": "PersonAdded",
"inputs": [
{ "type": "uint256", "name": "id", "indexed": true },
{
"type": "tuple",
"name": "person",
"components": [
{ "type": "string", "name": "name", "indexed": false },
{ "type": "uint16", "name": "age", "indexed": false }
]
}
]
}
]`;
The output from parsing (using JSON.parse) a Solidity JSON ABI is also fully compatible with the Interface class and each method, event and error from that object are compatible with the Fragment class.
Some developers may prefer this as it allows access to the ABI properties as normal JavaScript objects, and closely matches the JSON ABI that those familiar with the Solidity ABI will recognize.
Converting Between Formats
The Fragment object makes it simple to reformat a single method, event or error, however most developers will be interested in converting an entire ABI.
For production code it is recommended to inline the Human-Readable ABI as it makes it easy to see at a glance which methods, events and errors are available. It is also highly recommend to strip out unused parts of the ABI (such as admin methods) to further reduce code size.
Converting to Full Human-Readable ABI
const iface = new Interface(jsonAbi);
iface.format(FormatTypes.full);
// [
// 'constructor(string symbol, string name)',
// 'function transferFrom(address from, address to, uint256 value)',
// 'function balanceOf(address owner) view returns (uint256)',
// 'event Transfer(address indexed from, address indexed to, address value)',
// 'error InsufficientBalance(account owner, uint256 balance)',
// 'function addPerson(tuple(string name, uint16 age) person)',
// 'function addPeople(tuple(string name, uint16 age)[] person)',
// 'function getPerson(uint256 id) view returns (tuple(string name, uint16 age))',
// 'event PersonAdded(uint256 indexed id, tuple(string name, uint16 age) person)'
// ]
Converting to Minimal Human-Readable ABI
const iface = new Interface(jsonAbi);
iface.format(FormatTypes.minimal);
// [
// 'constructor(string,string)',
// 'function transferFrom(address,address,uint256)',
// 'function balanceOf(address) view returns (uint256)',
// 'event Transfer(address indexed,address indexed,address)',
// 'error InsufficientBalance(account,uint256)',
// 'function addPerson(tuple(string,uint16))',
// 'function addPeople(tuple(string,uint16)[])',
// 'function getPerson(uint256) view returns (tuple(string,uint16))',
// 'event PersonAdded(uint256 indexed,tuple(string,uint16))'
// ]
const iface = new Interface(humanReadableAbi);
jsonAbi = iface.format(FormatTypes.json);
// '[{"type":"constructor","payable":false,"inputs":[{"type":"string","name":"symbol"},{"type":"string","name":"name"}]},{"type":"function","name":"transferFrom","constant":false,"payable":false,"inputs":[{"type":"address","name":"from"},{"type":"address","name":"to"},{"type":"uint256","name":"value"}],"outputs":[]},{"type":"function","name":"balanceOf","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"owner"}],"outputs":[{"type":"uint256","name":"balance"}]},{"type":"event","anonymous":false,"name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"address","name":"value"}]},{"type":"error","name":"InsufficientBalance","inputs":[{"type":"account","name":"owner"},{"type":"uint256","name":"balance"}]},{"type":"function","name":"addPerson","constant":false,"payable":false,"inputs":[{"type":"tuple","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"addPeople","constant":false,"payable":false,"inputs":[{"type":"tuple[]","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"getPerson","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[{"type":"tuple","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}]},{"type":"event","anonymous":false,"name":"PersonAdded","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"tuple","name":"person","components":[{"type":"string","name":"name","indexed":false},{"type":"uint16","name":"age","indexed":false}]}]}]'
JSON.stringify(JSON.parse(jsonAbi), null, 2);
// `[
// {
// "type": "constructor",
// "payable": false,
// "inputs": [
// {
// "type": "string",
// "name": "symbol"
// },
// {
// "type": "string",
// "name": "name"
// }
// ]
// },
// {
// "type": "function",
// "name": "transferFrom",
// "constant": false,
// "payable": false,
// "inputs": [
// {
// "type": "address",
// "name": "from"
// },
// {
// "type": "address",
// "name": "to"
// },
// {
// "type": "uint256",
// "name": "value"
// }
// ],
// "outputs": []
// },
// {
// "type": "function",
// "name": "balanceOf",
// "constant": true,
// "stateMutability": "view",
// "payable": false,
// "inputs": [
// {
// "type": "address",
// "name": "owner"
// }
// ],
// "outputs": [
// {
// "type": "uint256",
// "name": "balance"
// }
// ]
// },
// {
// "type": "event",
// "anonymous": false,
// "name": "Transfer",
// "inputs": [
// {
// "type": "address",
// "name": "from",
// "indexed": true
// },
// {
// "type": "address",
// "name": "to",
// "indexed": true
// },
// {
// "type": "address",
// "name": "value"
// }
// ]
// },
// {
// "type": "error",
// "name": "InsufficientBalance",
// "inputs": [
// {
// "type": "account",
// "name": "owner"
// },
// {
// "type": "uint256",
// "name": "balance"
// }
// ]
// },
// {
// "type": "function",
// "name": "addPerson",
// "constant": false,
// "payable": false,
// "inputs": [
// {
// "type": "tuple",
// "name": "person",
// "components": [
// {
// "type": "string",
// "name": "name"
// },
// {
// "type": "uint16",
// "name": "age"
// }
// ]
// }
// ],
// "outputs": []
// },
// {
// "type": "function",
// "name": "addPeople",
// "constant": false,
// "payable": false,
// "inputs": [
// {
// "type": "tuple[]",
// "name": "person",
// "components": [
// {
// "type": "string",
// "name": "name"
// },
// {
// "type": "uint16",
// "name": "age"
// }
// ]
// }
// ],
// "outputs": []
// },
// {
// "type": "function",
// "name": "getPerson",
// "constant": true,
// "stateMutability": "view",
// "payable": false,
// "inputs": [
// {
// "type": "uint256",
// "name": "id"
// }
// ],
// "outputs": [
// {
// "type": "tuple",
// "components": [
// {
// "type": "string",
// "name": "name"
// },
// {
// "type": "uint16",
// "name": "age"
// }
// ]
// }
// ]
// },
// {
// "type": "event",
// "anonymous": false,
// "name": "PersonAdded",
// "inputs": [
// {
// "type": "uint256",
// "name": "id",
// "indexed": true
// },
// {
// "type": "tuple",
// "name": "person",
// "components": [
// {
// "type": "string",
// "name": "name",
// "indexed": false
// },
// {
// "type": "uint16",
// "name": "age",
// "indexed": false
// }
// ]
// }
// ]
// }
// ]`