Writing Contracts
Send transactions to modify smart contract state
Send transactions to modify smart contract state. Unlike read operations, writes require gas and must be signed.
Preparing a Contract Write
Use PrepareContractWrite to build a transaction for a contract call:
1import (2 "github.com/ethereum/go-ethereum/common"3 "github.com/ChefBingbong/viem-go/client"4 "math/big"5)67// Prepare the transaction8tx, err := publicClient.PrepareContractWrite(ctx, client.PrepareContractWriteOptions{9 Address: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),10 ABI: `[{11 "name": "transfer",12 "type": "function",13 "inputs": [14 {"name": "to", "type": "address"},15 {"name": "amount", "type": "uint256"}16 ],17 "outputs": [{"type": "bool"}]18 }]`,19 FunctionName: "transfer",20 Args: []any{21 common.HexToAddress("0xRecipient..."),22 big.NewInt(1000000), // 1 USDC (6 decimals)23 },24 From: senderAddress,25})26if err != nil {27 log.Fatal(err)28}2930fmt.Printf("To: %s\n", tx.To.Hex())31fmt.Printf("Data: 0x%x\n", tx.Data)32fmt.Printf("Gas: %d\n", tx.Gas)
Parameters
PrepareContractWrite uses PrepareContractWriteOptions. wallet.WriteContract uses WriteContractParameters; WriteContractSync uses WriteContractSyncParameters (embeds WriteContractParameters plus polling/timeout). wallet.DeployContract uses DeployContractParameters.
Address (required for write)
- Type:
common.Address(PrepareContractWrite) orstring(WriteContractParameters) - The contract address to call.
ABI (required)
- Type:
any—string,[]byte, or*abi.ABI - Contract ABI for encoding the function call.
FunctionName (required for write)
- Type:
string - The name of the function to call (e.g.
"transfer","approve").
Args (optional)
- Type:
[]any - Default:
nil - Function arguments in order.
From / Account (required for write)
- Type:
common.Address(PrepareContractWrite) or Account (WriteContractParameters: optional if client has default account) - Sender address. For WriteContract / WriteContractSync, omit if the Wallet Client has a default account.
Value (optional)
- Type:
*big.Int - Default:
nil - ETH to send with the transaction (for payable functions).
Gas, GasPrice, MaxFeePerGas, MaxPriorityFeePerGas (optional)
- Type:
uint64(Gas for PrepareContractWrite),*big.Int(Gas in wallet params),*big.Int(GasPrice, MaxFeePerGas, MaxPriorityFeePerGas) - Default: Gas is estimated if not set; fees from chain or client defaults.
- Override gas limit or fee parameters.
Nonce (optional)
- Type:
*uint64(PrepareContractWrite) or*int(wallet params) - Default: Fetched from chain (pending nonce) if not provided.
WriteContractSync-only: PollingInterval, ThrowOnReceiptRevert, Timeout (optional)
- PollingInterval:
time.Duration— interval to poll for receipt. Default: client’s polling interval. - ThrowOnReceiptRevert:
*bool— if true, return error when receipt status is reverted. Default: true. - Timeout:
*time.Duration— max wait for receipt. Default: derived from chain block time.
Signing and Sending
After preparing, sign and send the transaction:
1import (2 "github.com/ChefBingbong/viem-go/accounts"3 "github.com/ChefBingbong/viem-go/utils/transaction"4)56// 1. Create account from private key7account, _ := accounts.PrivateKeyToAccount("0x...")89// 2. Prepare the transaction10tx, _ := publicClient.PrepareContractWrite(ctx, client.PrepareContractWriteOptions{11 Address: contractAddress,12 ABI: abi,13 FunctionName: "transfer",14 Args: []any{recipient, amount},15 From: common.HexToAddress(account.Address),16})1718// 3. Convert to transaction for signing19signableTx := &transaction.Transaction{20 To: tx.To,21 Data: tx.Data,22 Value: tx.Value,23 Gas: tx.Gas,24 GasPrice: tx.GasPrice,25 Nonce: *tx.Nonce,26 ChainID: tx.ChainID,27}2829// 4. Sign the transaction30signedTx, err := account.SignTransaction(signableTx)31if err != nil {32 log.Fatal(err)33}3435// 5. Send the signed transaction36txHash, err := walletClient.SendRawTransaction(ctx, []byte(signedTx))37if err != nil {38 log.Fatal(err)39}4041fmt.Printf("Transaction sent: %s\n", txHash.Hex())4243// 6. Wait for receipt44receipt, err := publicClient.WaitForTransactionReceipt(ctx, txHash)45fmt.Printf("Status: %d\n", receipt.Status)
Using Typed Contract Bindings
The ERC20 binding includes write methods:
import "github.com/ChefBingbong/viem-go/contracts/erc20"
token, _ := erc20.New(usdcAddress, publicClient)
// Prepare a transfer
tx, err := token.PrepareTransfer(ctx, recipient, amount, senderAddress)
if err != nil {
log.Fatal(err)
}
// Sign and send...
Payable Functions
For functions that accept ETH, use the Value parameter:
tx, err := publicClient.PrepareContractWrite(ctx, client.PrepareContractWriteOptions{
Address: wethAddress,
ABI: `[{"name":"deposit","type":"function","inputs":[],"stateMutability":"payable"}]`,
FunctionName: "deposit",
From: senderAddress,
Value: big.NewInt(1e18), // 1 ETH
})
Encoding Function Data
If you need just the calldata without a full transaction:
1import "github.com/ChefBingbong/viem-go/client"23data, err := client.EncodeFunctionData(client.EncodeFunctionDataOptions{4 ABI: `[{5 "name": "transfer",6 "type": "function",7 "inputs": [8 {"name": "to", "type": "address"},9 {"name": "amount", "type": "uint256"}10 ]11 }]`,12 FunctionName: "transfer",13 Args: []any{recipient, amount},14})1516fmt.Printf("Calldata: 0x%x\n", data)17// Output: 0xa9059cbb000000000000000000000000...
Gas Estimation
Gas is automatically estimated, but you can override:
// Let it estimate (adds 20% buffer)
tx, _ := publicClient.PrepareContractWrite(ctx, client.PrepareContractWriteOptions{
// ...
})
fmt.Printf("Estimated gas: %d\n", tx.Gas)
// Or specify manually
tx, _ := publicClient.PrepareContractWrite(ctx, client.PrepareContractWriteOptions{
// ...
Gas: 100000,
})
DeployContract
To deploy a new contract, use wallet.DeployContract with bytecode and optional constructor args. See Deploy Contract.
EstimateContractGas
Estimate gas for a write without sending: use public.EstimateContractGas. See Estimate Contract Gas.
EncodeFunctionData
client.EncodeFunctionData(opts) returns only the calldata (selector + encoded args) without building a full transaction. Useful for multicall or custom tx building. See ABI Encoding for encoding with a parsed ABI.
Error handling
tx, err := publicClient.PrepareContractWrite(ctx, opts)
if err != nil {
if strings.Contains(err.Error(), "execution reverted") {
log.Println("Transaction would revert")
}
return err
}
See also
- Reading Contracts — ReadContract, SimulateContract
- Deploy Contract — DeployContract
- Estimate Contract Gas — EstimateContractGas
- Simulate Contract — test writes without sending
- ABI Encoding — EncodeFunctionData on *ABI