Creating Go Bindings for Ethereum Smart Contracts
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:
- Manually compiled Solidity contract
- Smart contracts with Truffle (eg. OpenZeppelin contracts)
- Compiling a contract on remix.ethereum.org
- Using the Etherscan ABI API
- Prebuilt Go bindings
Contents
Application Binary Interface (ABI)
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.
The abigen
tool
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:
Manually compiled Solidity contract
If you have a smart contract with full Solidity source code (eg. Store.sol
), 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
Smart contracts with Truffle
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 pugin:
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
Compiling a contract on remix.ethereum.org
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:
Getting the ABI from Etherscan
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 uniswap.abi
and generate the Go bindings like this:
abigen --abi uniswap.abi --pkg uniswap --out uniswap.go
Prebuilt Go bindings
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
Using the Go bindings
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)
}
References
- https://geth.ethereum.org/docs/dapp/native-bindings
- https://goethereumbook.org/en/smart-contract-compile/
- https://github.com/ethereum/go-ethereum
- https://docs.soliditylang.org/en/develop/abi-spec.html
- https://www.trufflesuite.com/docs/truffle/overview
- https://github.com/OpenZeppelin/openzeppelin-contracts
- http://remix.ethereum.org
- Examples: Store.sol, Store.abi, Store.go
- Prebuilt bindings:
- https://github.com/metachris/eth-go-bindings - standard smart contracts: ERC20, 165, 721, 777, 1155
- https://github.com/fxfactorial/defi-abigen - Aave, Chainlink price feed, Compound, Erc20, Onesplit and Uniswap (by @Edgar)
Reach out:
- @metachris
- Leave a comment below 👇