In the previous article, we explored how to generate private keys and sign transactions using Go with the go-ethereum library. Now, we’ll take it a step further by diving into smart contract interactions on the Ethereum blockchain. This includes reading public state variables, executing transactions, and handling events—all using Go.
This guide assumes you already know how to connect to an Ethereum node using Go. For setup details, refer to foundational tutorials on establishing a connection and configuring your Go development environment. Additionally, for insights into Solidity smart contract development, consult community resources focused on Ethereum programming.
Understanding Smart Contract Interactions
Interacting with Ethereum smart contracts from external applications relies on the JSON-RPC API. The primary methods used are:
- Reading state data: Use
eth_callto query contract variables without altering the blockchain. - Modifying state or deploying contracts: Use
eth_sendRawTransactionoreth_sendTransaction. These require transaction signing when changing contract state. - Listening to events: Use filter-based APIs like
eth_newFilterto subscribe to real-time event logs.
Under the hood, every function call is encoded using the Ethereum ABI (Application Binary Interface). This encoding process converts function names and parameters into hexadecimal data based on predefined rules for static and dynamic types. The encoded data is then included in the transaction's data field.
When deploying a contract, thetofield is set tonull, and only the compiled bytecode is sent in the transaction data. If a function receives Ether, it must be markedpayable. Similarly, fallback functions must also bepayableduring deployment if value transfers are expected.
👉 Discover how blockchain developers streamline smart contract testing and deployment
Setting Up the Solidity Development Environment
Before interacting with smart contracts in Go, we need to compile Solidity code and generate corresponding Go bindings.
Install Solidity Compiler
Use npm to install the Solidity compiler:
npm install -g solcNote: The installed command-line tool may be namedsolcjs. To avoid issues withabigen, ensure compatibility by creating an alias or symlink if necessary.
Compile Smart Contract Files
Compile .sol files to generate both binary (--bin) and ABI (--abi) outputs:
solcjs --bin --abi HW.sol EIP20.sol SafeMath.sol EIP20interface.solThis generates files like HW_sol_HW.bin and HW_sol_HW.abi, which are essential for Go integration.
Install abigen Tool
The abigen tool converts Solidity contracts into Go packages:
go get github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum/
make devtoolsGenerate Go Bindings
Create a Go file from the compiled contract:
abigen --bin HW_sol_HW.bin --abi HW_sol_HW.abi --pkg contract --out HW.goThis generates a type-safe Go package that allows seamless interaction with the contract functions.
We’ll now use an ERC-20 token as a practical example to demonstrate:
- Deploying an ERC-20 contract
- Querying a user’s token balance
- Listening for
Transferevents - Sending tokens to another address
Deploying an ERC-20 Token Contract
There are two main ways to deploy a smart contract using Go: manually constructing a transaction or using auto-generated deployment functions.
Method 1: Manually Creating and Signing a Transaction
amount := big.NewInt(0)
gasLimit := uint64(4600000)
gasPrice := big.NewInt(1000000000)
data := common.FromHex(Contract.ContractBin)
tx := types.NewContractCreation(nonce, amount, gasLimit, gasPrice, data)
signer := types.HomesteadSigner{} // or EIP155Signer for chain-specific signing
signedTx, _ := types.SignTx(tx, signer, privKey)Method 2: Using Generated Deploy Function
First, define a helper function to create transaction options:
func makeTxOpts(from common.Address, nonce *big.Int, value *big.Int, gasPrice *big.Int, gasLimit uint64, privKey *ecdsa.PrivateKey, chainID int64) *bind.TransactOpts {
return &bind.TransactOpts{
From: from,
Nonce: nonce,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
var txSigner types.Signer
if chainID != 0 {
txSigner = types.NewEIP155Signer(big.NewInt(chainID))
} else {
txSigner = signer
}
return types.SignTx(tx, txSigner, privKey)
},
Value: value,
GasPrice: gasPrice,
GasLimit: gasLimit,
}
}Then deploy using the generated DeployContract function:
non := big.NewInt(int64(nonce))
txOpts := makeTxOpts(from, non, amount, gasPrice, gasLimit, privKey, 0)
contractAddress, deployTx, contractInstance, err := Contract.DeployContract(txOpts, client.EthClient)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("Contract deployed at:", contractAddress.Hex())
fmt.Println("Transaction hash:", deployTx.Hash().String())
}The contract address is computed deterministically from the sender and nonce. However, confirmation via transaction receipt is required to ensure successful deployment.
👉 Learn how top developers monitor blockchain transactions in real time
Querying Token Balance
After deployment, retrieve a user’s token balance using the generated contract binding:
contract, _ := Contract.NewContract(contractAddress, client.EthClient)
from := common.HexToAddress("0x9b23a6a9a60b3846f86ebc451d11bef20ed07930")
balance, _ := contract.BalanceOf(nil, from)
fmt.Printf("Balance of %s is %d\n", from.Hex(), balance)This uses eth_call under the hood—no gas cost or signing required.
Listening to Transfer Events
Events are pushed in real time using WebSockets. Ensure your Ethereum node supports WebSocket connections (e.g., via wss:// endpoints).
ch := make(chan *Contract.ContractTransfer)
sub, _ := contract.WatchTransfer(nil, ch, nil, nil)
go func() {
for {
select {
case err := <-sub.Err():
fmt.Println("Subscription error:", err)
return
case event := <-ch:
fmt.Println("[Transfer Event]")
fmt.Printf("From: %s\n", event.From.Hex())
fmt.Printf("To: %s\n", event.To.Hex())
fmt.Printf("Value: %d\n", event.Value)
}
}
}()
// Keep the program running to receive events
select {}If WebSockets aren't available, use log filtering:
filterOpts := &bind.FilterOpts{Start: 0}
logs, err := contract.FilterTransfer(filterOpts, nil, nil)
if err != nil {
fmt.Println("Filter error:", err)
} else {
for _, vLog := range logs {
fmt.Printf("Past Transfer: %s -> %s (%d)\n", vLog.From.Hex(), vLog.To.Hex(), vLog.Value)
}
}Sending Tokens (Transfer Function)
To transfer tokens, call the Transfer method with recipient address and amount:
txOpts := makeTxOpts(from, non, amount, gasPrice, gasLimit, privKey, 0)
toAddress := common.HexToAddress("0x9b23a6a9a60b3846f86ebc451d11bef20ed07930")
value := big.NewInt(10000)
transferTx, err := contract.Transfer(txOpts, toAddress, value)
if err != nil {
fmt.Println("Transfer failed:", err)
} else {
fmt.Println("Transfer initiated. Tx hash:", transferTx.Hash().Hex())
}This generates and signs a transaction that modifies the contract state.
Frequently Asked Questions (FAQ)
Q: Do I need Ether to read contract data?
A: No. Reading state via eth_call is free and doesn’t require gas since it doesn’t modify the blockchain.
Q: What is abigen used for?
A: abigen generates Go wrappers from Solidity contracts, enabling type-safe function calls and event parsing without manual ABI encoding.
Q: Can I interact with existing contracts like USDT or DAI?
A: Yes. As long as you have the contract’s ABI and address, you can generate Go bindings and interact with any public Ethereum contract.
Q: Why use EIP155 signer?
A: EIP155 prevents replay attacks across chains by including the chain ID in the signature. It’s essential for production deployments.
Q: How do I confirm a transaction succeeded?
A: Use client.TransactionReceipt(ctx, tx.Hash()) to wait for and verify the transaction’s inclusion in a block.
Q: Is WebSocket required for all event listening?
A: For real-time events (WatchX), yes. For historical logs (FilterX), you can use standard HTTP endpoints.
Final Thoughts
This guide covered core aspects of interacting with Ethereum smart contracts using Go—from compiling Solidity code to deploying contracts and handling events. By leveraging tools like abigen and the go-ethereum library, developers can build robust backend services for decentralized applications.
For full code examples and utilities, explore open-source repositories that demonstrate these patterns in practice.
👉 Explore advanced tools for Ethereum developers building secure dApps