Reading Contracts
Read data from smart contracts using view and pure functions
Read data from smart contracts using view and pure functions. These calls don't require gas since they don't modify state.
Basic Usage
1import (2 "github.com/ethereum/go-ethereum/common"3 "github.com/ChefBingbong/viem-go/client"4)56result, err := publicClient.ReadContract(ctx, client.ReadContractOptions{7 Address: common.HexToAddress("0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2"),8 ABI: `[{"name":"totalSupply","type":"function","inputs":[],"outputs":[{"type":"uint256"}]}]`,9 FunctionName: "totalSupply",10})11if err != nil {12 log.Fatal(err)13}1415totalSupply := result.(*big.Int)16fmt.Println("Total Supply:", totalSupply)
Passing Arguments
Pass function arguments using the Args slice:
1result, err := publicClient.ReadContract(ctx, client.ReadContractOptions{2 Address: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),3 ABI: `[{4 "name": "balanceOf",5 "type": "function",6 "inputs": [{"name": "owner", "type": "address"}],7 "outputs": [{"type": "uint256"}]8 }]`,9 FunctionName: "balanceOf",10 Args: []any{11 common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"),12 },13})1415balance := result.(*big.Int)
Parameters
The Public Client’s ReadContract uses ReadContractOptions. The contract package’s ReadContract[T] uses ReadContractParams. Both support the same concepts (address, ABI, function name, args, block).
Address (required)
- Type:
common.Address - The contract address to call.
ABI (required)
- Type:
any—string,[]byte, or*abi.ABI - The contract ABI as JSON bytes, a JSON string, or a pre-parsed *abi.ABI from abi.Parse.
FunctionName (required)
- Type:
string - The name of the view/pure function to call (e.g.
"balanceOf","totalSupply").
Args (optional)
- Type:
[]any - Default:
nil - Function arguments in order. Omit for functions with no parameters.
From (optional)
- Type:
*common.Address - Default:
nil - Caller address used for the call. Some contracts use this for access control or modifiers.
Block / BlockTag (optional)
- Type:
client.BlockTag(e.g.client.BlockTagLatest,"safe","0x1234") or BlockTag in ReadContractParams - Default: latest
- Block at which to execute the read (e.g.
client.BlockTagSafe, hex block number).
Return type
- PublicClient.ReadContract(ctx, opts) returns
(any, error). The decoded value is the first return value (e.g.*big.Int,string,common.Address). For multiple return values, use contract.ReadContract with a struct type or SimulateContract which returns[]any. - contract.ReadContract[T](client, params) returns
(T, error)with type-safe result (e.g.ReadContract[*big.Int],ReadContract[string]). Use contract.ReadContractWithContext when you need to pass a context.
SimulateContract
Use SimulateContract to run a contract call (including state-changing functions) without sending a transaction. Useful to check revert reasons or return values before writing. See Simulate Contract.
result, err := publicClient.SimulateContract(ctx, client.SimulateContractOptions{
ReadContractOptions: client.ReadContractOptions{
Address: contractAddress,
ABI: abi,
FunctionName: "transfer",
Args: []any{recipient, amount},
From: &sender,
},
Value: big.NewInt(0),
})
Using Typed Contract Bindings
For frequently used contracts, viem-go provides pre-built typed bindings:
1import "github.com/ChefBingbong/viem-go/contracts/erc20"23// Create typed ERC20 instance4usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")5token, err := erc20.New(usdc, publicClient)6if err != nil {7 log.Fatal(err)8}910// Call typed methods - no ABI needed11balance, err := token.BalanceOf(ctx, ownerAddress)12name, err := token.Name(ctx)13symbol, err := token.Symbol(ctx)14decimals, err := token.Decimals(ctx)15totalSupply, err := token.TotalSupply(ctx)16allowance, err := token.Allowance(ctx, owner, spender)
Available Bindings
| Contract | Import | Methods |
|---|---|---|
| ERC20 | contracts/erc20 | BalanceOf, Name, Symbol, Decimals, TotalSupply, Allowance |
| ERC721 | contracts/erc721 | BalanceOf, OwnerOf, Name, Symbol, TokenURI, GetApproved |
| ERC1155 | contracts/erc1155 | BalanceOf, BalanceOfBatch, URI, IsApprovedForAll |
Reading at Specific Blocks
Query contract state at a specific block:
// Read at block number
result, err := publicClient.ReadContract(ctx, client.ReadContractOptions{
Address: contractAddress,
ABI: abi,
FunctionName: "totalSupply",
Block: "0xE8D4A0", // Block number as hex
})
// Read at block tag
result, err := publicClient.ReadContract(ctx, client.ReadContractOptions{
Address: contractAddress,
ABI: abi,
FunctionName: "totalSupply",
Block: client.BlockTagSafe,
})
Error Handling
Contract calls can fail for various reasons:
result, err := publicClient.ReadContract(ctx, client.ReadContractOptions{
Address: contractAddress,
ABI: abi,
FunctionName: "balanceOf",
Args: []any{owner},
})
if err != nil {
// Check for revert
if strings.Contains(err.Error(), "execution reverted") {
log.Println("Contract reverted")
}
// Check for invalid function
if strings.Contains(err.Error(), "method not found") {
log.Println("Function doesn't exist on contract")
}
return err
}
See also
- Writing Contracts — PrepareContractWrite, WriteContract
- Simulate Contract — SimulateContract
- Get Code — GetCode
- Multicall — batch readContract calls
- ABI Decoding — DecodeFunctionResult
Type Conversions
The result type depends on the Solidity return type:
| Solidity Type | Go Type |
|---|---|
uint256, int256 | *big.Int |
uint8 - uint128 | *big.Int |
address | common.Address |
bool | bool |
string | string |
bytes | []byte |
bytes32 | [32]byte |
tuple | struct or map[string]any |
array | slice |
// Casting results
balance := result.(*big.Int)
owner := result.(common.Address)
approved := result.(bool)
name := result.(string)
data := result.([]byte)