Writing Contracts
Send transactions to modify smart contract state
Send transactions to modify smart contract state
Send transactions to modify smart contract state. Unlike read operations, writes require gas and must be signed.
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)6
7// 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}29
30fmt.Printf("To: %s\n", tx.To.Hex())31fmt.Printf("Data: 0x%x\n", tx.Data)32fmt.Printf("Gas: %d\n", tx.Gas)PrepareContractWrite uses PrepareContractWriteOptions. wallet.WriteContract uses WriteContractParameters; WriteContractSync uses WriteContractSyncParameters (embeds WriteContractParameters plus polling/timeout). wallet.DeployContract uses DeployContractParameters.
common.Address (PrepareContractWrite) or string (WriteContractParameters)any — string, []byte, or *abi.ABIstring"transfer", "approve").[]anynilcommon.Address (PrepareContractWrite) or Account (WriteContractParameters: optional if client has default account)*big.Intniluint64 (Gas for PrepareContractWrite), *big.Int (Gas in wallet params), *big.Int (GasPrice, MaxFeePerGas, MaxPriorityFeePerGas)*uint64 (PrepareContractWrite) or *int (wallet params)time.Duration — interval to poll for receipt. Default: client’s polling interval.*bool — if true, return error when receipt status is reverted. Default: true.*time.Duration — max wait for receipt. Default: derived from chain block time.After preparing, sign and send the transaction:
1import (2 "github.com/ChefBingbong/viem-go/accounts"3 "github.com/ChefBingbong/viem-go/utils/transaction"4)5
6// 1. Create account from private key7account, _ := accounts.PrivateKeyToAccount("0x...")8
9// 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})17
18// 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}28
29// 4. Sign the transaction30signedTx, err := account.SignTransaction(signableTx)31if err != nil {32 log.Fatal(err)33}34
35// 5. Send the signed transaction36txHash, err := walletClient.SendRawTransaction(ctx, []byte(signedTx))37if err != nil {38 log.Fatal(err)39}40
41fmt.Printf("Transaction sent: %s\n", txHash.Hex())42
43// 6. Wait for receipt44receipt, err := publicClient.WaitForTransactionReceipt(ctx, txHash)45fmt.Printf("Status: %d\n", receipt.Status)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...
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
})
If you need just the calldata without a full transaction:
1import "github.com/ChefBingbong/viem-go/client"2
3data, 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})15
16fmt.Printf("Calldata: 0x%x\n", data)17// Output: 0xa9059cbb000000000000000000000000...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,
})
To deploy a new contract, use wallet.DeployContract with bytecode and optional constructor args. See Deploy Contract.
Estimate gas for a write without sending: use public.EstimateContractGas. See Estimate Contract Gas.
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.
tx, err := publicClient.PrepareContractWrite(ctx, opts)
if err != nil {
if strings.Contains(err.Error(), "execution reverted") {
log.Println("Transaction would revert")
}
return err
}