Decoding Ethereum ABIs with Go: A Developer’s Guide

Aravind Sai Vellappat
3 min readDec 13, 2024

--

Photo by Tokenstreet on Unsplash

Understanding ABIs

An ABI is a JSON-formatted file that describes the structure of a smart contract, including:

  1. The functions it exposes.
  2. The parameters each function accepts.
  3. The return values of functions.
  4. Events emitted by the contract.

It acts as a blueprint, allowing users or applications to understand how to call a smart contract’s methods and handle its responses.

Since smart contracts operate in bytecode on the Ethereum Virtual Machine (EVM), human-readable function names and structures must be translated into this bytecode. ABIs provide the map for encoding data into the required binary format.

ABI parsing in GoLang

In GoLang, the ABI, which is usually written as JSON, needs to be converted into Go-encoded ABI before being used for encoding or decoding.

We will be using the go-ethereum package here. It can be added to the project repo using

go get github.com/ethereum/go-ethereum

ABI Example

{
"type": "function",
"name": "encode",
"inputs": [
{
"name": "data",
"type": "bytes"
}
],
"outputs": [
{
"components": [
{
"name": "transferId",
"type": "string"
},
{
"name": "collectionName",
"type": "string"
},
{
"name": "tokens",
"type": "tuple",
"components": [
{
"name": "amount",
"type": "uint256"
},
{
"name": "tokenId",
"type": "string"
}
]
}
],
"type": "tuple",
"name": "transfers"
}
],
"stateMutability": "nonpayable"
}

This can be converted into Go-encoded ABI format as,

transferAbi, err := abi.NewType("tuple", "data", []abi.ArgumentMarshalling{
{Name: "transferId", Type: "string"},
{Name: "collectionName", Type: "string"},
{Name: "tokens", Type: "tuple", Components: []abi.ArgumentMarshalling{
{Name: "tokenId", Type: "string"},
{Name: "amount", Type: "uint256"},
}},
})

Once we have the ABIs in place, we can use them to encode and decode the data.

Let’s consider the data is a hex-encoded byte array and we need to decode using the above ABI. We initially convert it into a byte array using the hexutil library in the Ethereum library. Then use the byte array for parsing using the ABI.

The complete workflow has been illustrated in the code below.

package main

import (
"fmt"
"math/big"

"fmt"
"math/big"

"github.com/ethreum/go-ethereum/accounts/abi"
"github.com/ethreum/go-ethereum/common/hexutil"
)

func main() {
transferAbi, err := abi.NewType("tuple", "data", []abi.ArgumentMarshalling{
{Name: "transferId", Type: "string"},
{Name: "collectionName", Type: "string"},
{Name: "tokens", Type: "tuple", Components: []abi.ArgumentMarshalling{
{Name: "tokenId", Type: "string"},
{Name: "amount", Type: "uint256"},
}},
})

hexString := "hex encoded string..."

decoded, err := decode(transferAbi, hexString)
if err != nil {
fmt.Println(err)
}

result := decoded[0].(struct {
TransferId string `json:"transferId"`
CollectionName string `json:"collectionName"`
Tokens []struct {
TokenId string `json:"tokenId"`
Amount *big.Int `json:"amount"`
} `json:"tokens"`
})

fmt.Println(result)

}

func decode(args abi.Arguments, data string) ([]interface{}, error) {
byteArr, err := hexutil.Decode(data)
if err != nil {
return nil, err
}
unpacked, err := args.Unpack(byteArr)
if err != nil {
return nil, err
}
return unpacked, nil
}

}

The same can be done in the case of encoding. Just use the abi.Pack() function. The encoding function can be as follows

func encode(args abi.Arguments, value interface{}) (string, error) {
packed, err := args.Pack(value)
if err != nil {
return nil, err
}
return hexutil.Encode(packed), nil
}

--

--

Aravind Sai Vellappat
Aravind Sai Vellappat

Written by Aravind Sai Vellappat

Entrepreneur and Developer, a hard but spicy mix up.

No responses yet