description: Tutorial on how to transfer ETH to another wallet or smart contract with Go.

Transferring ETH

In this lesson you’ll learn how to transfer ETH from one account to another account. If you’re already familar with Ethereum then you know that a transaction consists of the amount of ether you’re transferring, the gas limit, the gas price, a nonce, the receiving address, and optionally data. The transaction must be signed with the private key of the sender before it’s broadcasted to the network.

Assuming you’ve already connected a client, the next step is to load your private key.

  1. privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
  2. if err != nil {
  3. log.Fatal(err)
  4. }

Afterwards we need to get the account nonce. Every transaction requires a nonce. A nonce by definition is a number that is only used once. If it’s a new account sending out a transaction then the nonce will be 0. Every new transaction from an account must have a nonce that the previous nonce incremented by 1. It’s hard to keep manual track of all the nonces so the ethereum client provides a helper method PendingNonceAt that will return the next nonce you should use.

The function requires the public address of the account we’re sending from — which we can derive from the private key.

  1. publicKey := privateKey.Public()
  2. publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
  3. if !ok {
  4. log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
  5. }
  6. fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

Here, privateKey.Public() returns an interface that contains our public key. We perform a type assertion with publicKey.(<expectedType>) to explictly set the type of our publicKey variable, and assign it to publicKeyECDSA. This allows us to use it where our program expects an input of type *ecdsa.PublicKey.

Now we can read the nonce that we should use for the account’s transaction.

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

The next step is to set the amount of ETH that we’ll be transferring. However we must convert ether to wei since that’s what the Ethereum blockchain uses. Ether supports up to 18 decimal places so 1 ETH is 1 plus 18 zeros. Here’s a little tool to help you convert between ETH and wei: https://etherconverter.netlify.com

  1. value := big.NewInt(1000000000000000000) // in wei (1 eth)

The gas limit for a standard ETH transfer is 21000 units.

  1. gasLimit := uint64(21000) // in units

The gas price must be set in wei. At the time of this writing, a gas price that will get your transaction included pretty fast in a block is 30 gwei.

  1. gasPrice := big.NewInt(30000000000) // in wei (30 gwei)

However, gas prices are always fluctuating based on market demand and what users are willing to pay, so hardcoding a gas price is sometimes not ideal. The go-ethereum client provides the SuggestGasPrice function for getting the average gas price based on x number of previous blocks.

  1. gasPrice, err := client.SuggestGasPrice(context.Background())
  2. if err != nil {
  3. log.Fatal(err)
  4. }

We figure out who we’re sending the ETH to.

  1. toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")

Now we can finally generate our unsigned ethereum transaction by importing the go-ethereum core/types package and invoking NewTransaction which takes in the nonce, to address, value, gas limit, gas price, and optional data. The data field is nil for just sending ETH. We’ll be using the data field when it comes to interacting with smart contracts.

  1. tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, nil)

The next step is to sign the transaction with the private key of the sender. To do this we call the SignTx method that takes in the unsigned transaction and the private key that we constructed earlier. The SignTx method requires the EIP155 signer, which we derive the chain ID from the client.

  1. chainID, err := client.NetworkID(context.Background())
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
  6. if err != nil {
  7. log.Fatal(err)
  8. }

Now we are finally ready to broadcast the transaction to the entire network by calling SendTransaction on the client which takes in the signed transaction.

  1. err = client.SendTransaction(context.Background(), signedTx)
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e

Afterwards you can check the progress on a block explorer such as Etherscan: https://rinkeby.etherscan.io/tx/0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e

Full code

transfer_eth.go

  1. package main
  2. import (
  3. "context"
  4. "crypto/ecdsa"
  5. "fmt"
  6. "log"
  7. "math/big"
  8. "github.com/ethereum/go-ethereum/common"
  9. "github.com/ethereum/go-ethereum/core/types"
  10. "github.com/ethereum/go-ethereum/crypto"
  11. "github.com/ethereum/go-ethereum/ethclient"
  12. )
  13. func main() {
  14. client, err := ethclient.Dial("https://rinkeby.infura.io")
  15. if err != nil {
  16. log.Fatal(err)
  17. }
  18. privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
  19. if err != nil {
  20. log.Fatal(err)
  21. }
  22. publicKey := privateKey.Public()
  23. publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
  24. if !ok {
  25. log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
  26. }
  27. fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
  28. nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. value := big.NewInt(1000000000000000000) // in wei (1 eth)
  33. gasLimit := uint64(21000) // in units
  34. gasPrice, err := client.SuggestGasPrice(context.Background())
  35. if err != nil {
  36. log.Fatal(err)
  37. }
  38. toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
  39. var data []byte
  40. tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
  41. chainID, err := client.NetworkID(context.Background())
  42. if err != nil {
  43. log.Fatal(err)
  44. }
  45. signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
  46. if err != nil {
  47. log.Fatal(err)
  48. }
  49. err = client.SendTransaction(context.Background(), signedTx)
  50. if err != nil {
  51. log.Fatal(err)
  52. }
  53. fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
  54. }