How to Send an ETH Transaction Using Go

·

Sending Ether (ETH) is one of the most fundamental operations on the Ethereum blockchain. Whether you're building a decentralized application or simply transferring funds, understanding how to construct, sign, and broadcast a transaction is essential. This guide will walk you through the entire process programmatically using the Go programming language (Golang) and the go-ethereum library.

Prerequisites for ETH Transfers

Before you begin, ensure you have a basic understanding of Ethereum transactions. A standard ETH transfer transaction includes several key components:

Crucially, every transaction must be cryptographically signed with the sender's private key before it is valid and can be sent to the network.

Step-by-Step: Building a Transaction in Go

Assuming you already have a connected ethclient, the following steps outline the process of creating and sending a transaction.

1. Load Your Private Key

The private key is required to sign the transaction and prove ownership of the sending account. Always handle private keys with extreme care and never expose them in production code.

privateKey, err := crypto.HexToECDSA("your_private_key_hex_string_here")
if err != nil {
    log.Fatal(err)
}

2. Derive the Public Address

From the private key, you can derive the corresponding public key and then the Ethereum public address. This address is used to identify your account on the network.

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
    log.Fatal("Error casting public key to ECDSA type")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

3. Obtain the Account Nonce

The nonce is a critical counter that prevents replay attacks. It must be unique for each transaction from your address. You can retrieve the next available nonce for your account using the client.

nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
    log.Fatal(err)
}

4. Set the Transfer Value

Ethereum transactions are denominated in wei, the smallest unit of ETH, where 1 ETH equals 1,000,000,000,000,000,000 wei (10^18). Use the big.Int type to handle these large numbers.

value := big.NewInt(1000000000000000000) // This represents 1 ETH

5. Configure Gas Settings

Every transaction consumes gas. A standard ETH transfer requires a gas limit of 21,000 units. The gas price, which you pay per unit of gas, can be set manually or fetched as a suggestion from the network.

gasLimit := uint64(21000) // Standard gas limit for ETH transfers
gasPrice, err := client.SuggestGasPrice(context.Background()) // Fetch current suggested price
if err != nil {
    log.Fatal(err)
}

6. Define the Recipient Address

Specify the destination address for the ETH transfer.

toAddress := common.HexToAddress("0xRecipientAddressHexHere")

7. Create the Unsigned Transaction

With all components prepared, you can now create the transaction object. For a simple transfer, the data field is left empty.

txData := []byte{} // Empty data for simple ETH send
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, txData)

8. Sign the Transaction

To authorize the transaction, you must sign it with your private key. This requires knowing the network's chain ID.

chainID, err := client.NetworkID(context.Background())
if err != nil {
    log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
    log.Fatal(err)
}

9. Broadcast the Transaction

The final step is to send the signed transaction to the Ethereum network for processing. 👉 Explore more strategies for transaction management

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Transaction successfully sent with hash: %s", signedTx.Hash().Hex())

You can then use the returned transaction hash to track its status on a block explorer like Etherscan.

Complete Go Code Example

Here is the consolidated code for sending an ETH transaction using Go.

package main

import (
    "context"
    "crypto/ecdsa"
    "fmt"
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    // 1. Connect to an Ethereum node
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    // 2. Load private key
    privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
    if err != nil {
        log.Fatal(err)
    }

    // 3. Derive public address
    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("Cannot assert type: publicKey is not of type *ecdsa.PublicKey")
    }
    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

    // 4. Get the account nonce
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    // 5. Set value (1 ETH in wei)
    value := big.NewInt(1000000000000000000)

    // 6. Set gas parameters
    gasLimit := uint64(21000)
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    // 7. Set recipient address
    toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")

    // 8. Create transaction
    var data []byte
    tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)

    // 9. Sign the transaction
    chainID, err := client.NetworkID(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    if err != nil {
        log.Fatal(err)
    }

    // 10. Send the transaction
    err = client.SendTransaction(context.Background(), signedTx)
    if err != nil {
        log.Fatal(err)
    }

    // 11. Print transaction hash
    fmt.Printf("Transaction sent: %s", signedTx.Hash().Hex())
}

Frequently Asked Questions

What is a nonce in Ethereum?
A nonce is a number that increments with each transaction sent from a specific Ethereum address. It ensures each transaction is unique and prevents double-spending or replay attacks. The first transaction from an account has a nonce of 0.

Why is 21,000 gas the standard limit for ETH transfers?
A simple transfer of ETH (without any smart contract interaction) requires a fixed amount of computational work for the network to process. This work has been standardized to cost 21,000 gas units, covering the cost of updating account balances and logging the transaction.

What happens if I set the gas price too low?
If your gas price is set significantly lower than the current network average, miners are less likely to prioritize your transaction. This can result in your transaction taking a very long time to confirm or getting stuck. You can resubmit the transaction with a higher gas price and the same nonce to replace it.

Can I send a transaction without using SuggestGasPrice?
Yes, you can manually set a gas price using big.NewInt and a value in wei. However, using the suggested gas price helps ensure your transaction is processed in a timely manner based on current network congestion.

Is the data field required for ETH transfers?
No, the data field is optional and is typically left empty for simple transfers of value. It is primarily used when interacting with smart contracts to call functions and pass parameters.

How can I track my transaction after sending it?
After broadcasting the transaction, you will receive a transaction hash. You can use this hash to look up the transaction's status, block confirmation, and other details on a blockchain explorer like Etherscan or Blockscout. 👉 View real-time tools for tracking blockchain activity