To interact with Ethereum smart contracts in Go programs, you need bindings for the specific type of contract. This post is a quick guide for generating these bindings from various sources:
The Application Binary Interface (ABI) of a smart contract describes how you call its functions and what kind of data you get back. The Go bindings are generated from such an ABI.
The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type, as described in this specification. The encoding is not self describing and thus requires a schema in order to decode.
The ABI usually has the form of a JSON file which can be generated from the smart contract source code. Take a look at this example ABI JSON. The following sections explain how to create (or get) the ABI from various forms of smart contracts, and to generate Go bindings from it.
abigen is used to generate the Go bindings from an ABI JSON file, and is part of go-ethereum. Make sure you have the latest version of it!
You can build, install and run abigen
like this:
# Download go-ethereum, build and install the devtools (which includes abigen)
git clone <https://github.com/ethereum/go-ethereum.git>
cd go-ethereum
make devtools
# Run abigen and print the version
abigen -version
abigen -help
Examples for how to generate Go bindings with abigen
:
# Create Go bindings from an ABI file (without bytecode)
abigen --abi <input-file.abi> --pkg <go-package-name> --out <output-file.go>
# Create Go bindings from an ABI + BIN file (with bytecode)
abigen --abi <input-file.abi> --bin <input-file.bin> --pkg <go-package-name> --out <output-file.go>
# Create Go bindings from a Solidity source file (with bytecode)
abigen --sol <input-file.sol> --pkg <go-package-name> --out <output-file.go>
See also:
If you have a smart contract with full Solidity source code (eg. Store.sol
):
/ SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
you can compile it with solc
and generate the bindings like this:
# Create the ABI (writes a file called build/Store.abi)
solc --abi Store.sol -o build
# Generate the Go bindings (without bytecode)
abigen --abi build/Store.abi --pkg store --out store.go
If you want to include the contract bytecode (needed to deploy the contract):
# Create ABI and bytecode
solc --abi Store.sol -o build
solc --bin Store.sol -o build
# Generate the Go bindings (with bytecode)
abigen --abi build/Store.abi --bin build/Store.bin --pkg store --out store.go
abigen
can also generate the bindings with bytecode automatically in one command:
abigen --sol Store.sol --pkg store --out store.go
Truffle is a popular Ethereum smart contract development toolkit. It is often paired with Ganache - a tool to run local Ethereum blockchains for development. Truffle quickstart docs offer a brief introduction into the whole workflow.
Let’s create a minimal ERC721 (NFT) smart contract based on the OpenZeppelin ERC721 contract.
Step 1: Setup the project and install dependencies (Truffle, OpenZeppelin contracts and @chainsafe/truffle-plugin-abigen):
# Create the project directory
mkdir mynft && cd mynft
# Install dependencies
yarn init -y
yarn add truffle @openzeppelin/contracts @chainsafe/truffle-plugin-abigen
# Run the Truffle project setup (creates the folder structure)
yarn truffle init
Step 2: Create the NFT721 smart contract (contracts/MyNFT.sol
):
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
constructor() ERC721("MyNFT", "MNFT") {}
}
Step 3: Edit truffle-config.json
to set the Solidity compiler version and adding the abigen
plugin:
module.exports = {
plugins: [
"@chainsafe/truffle-plugin-abigen"
],
compilers: {
solc: {
version: "0.8.4", // Fetch exact version from solc-bin (default: truffle's version)
}
}
}
Step 4: Compile the contract and ABI and build the Go interface
# Compile the contract
$ yarn truffle compile
Compiling your contracts
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/MyNFT.sol
> Compiling @openzeppelin/contracts/token/ERC721/ERC721.sol
> Compiling @openzeppelin/contracts/token/ERC721/IERC721.sol
> Compiling @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol
> Compiling @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol
> Compiling @openzeppelin/contracts/utils/Address.sol
> Compiling @openzeppelin/contracts/utils/Context.sol
> Compiling @openzeppelin/contracts/utils/Strings.sol
> Compiling @openzeppelin/contracts/utils/introspection/ERC165.sol
> Compiling @openzeppelin/contracts/utils/introspection/IERC165.sol
> Artifacts written to build/contracts
> Compiled successfully using:
- solc: 0.8.4+commit.c7e474f2.Emscripten.clang
# Generate the ABI and BIN files (stored in abigenBindings/)
$ yarn truffle run abigen MyNFT
# Create the Go binding with bytecode (~61K)
$ abigen --bin=abigenBindings/bin/MyNFT.bin --abi=abigenBindings/abi/MyNFT.abi --pkg=mynft --out=mynft.go
# Alternatively, create the Go binding without the bytecode (~42K)
$ abigen --abi=abigenBindings/abi/MyNFT.abi --pkg=mynft --out=mynft.go
You can use remix.ethereum.org to compile smart contracts and download the ABI file like this:
Save the copied ABI into a file called store.abi
and create the Go bindings with abigen
:
abigen --abi store.abi --pkg store --out store.go
Example code, ABI and Go bindings:
Etherscan provides ABI downloads for verified smart contracts through the API and website.
Example API call to get the Uniswap V3 Router contract ABI:
<https://api.etherscan.io/api?module=contract&action=getabi&address=0xE592427A0AEce92De3Edee1F18E0157C05861564>
Response:
{
"status": "1",
"message": "OK-Missing/Invalid API Key, rate limit of 1/5sec applied",
"result": "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
}
Or you can manually export the ABI by visiting the “Contract” tab and scrolling down to the “Contract ABI” text area:
You can save this as uniswapv3.abi
and generate the Go bindings like this:
abigen --abi uniswapv3.abi --pkg uniswapv3 --out uniswapv3.go
You can find prebuilt Go bindings on the internet, although many of them target older versions of Go and are not compatible. Here’s links to some good repositories:
github.com/fxfactorial/defi-abigen is a great collection of bindings for Aave, Chainlink price feed, Compound, Erc20, Onesplit and Uniswap (made by @Edgar) github.com/metachris/eth-go-bindings has bindings for ERC20, 165, 721, 777 and ERC1155 smart contracts
This is an example of using the Go bindings to call a contract method (eg. name()
of a token contract):
func main() {
// Connect to a geth node (when using Infura, you need to use your own API key)
conn, err := ethclient.Dial("https://mainnet.infura.io/v3/7238211010344719ad14a89db874158c")
if err != nil {
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
}
// Instantiate the contract and display its name
address := common.HexToAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984")
token, err := NewToken(address, conn)
if err != nil {
log.Fatalf("Failed to instantiate a Token contract: %v", err)
}
// Access token properties
name, err := token.Name(nil)
if err != nil {
log.Fatalf("Failed to retrieve token name: %v", err)
}
fmt.Println("Token name:", name)
}