Sending ERC-20 tokens on the Ethereum blockchain requires a different approach than sending native ETH. This guide walks you through the process step-by-step using the Go Ethereum (Geth) library and EIP-1559 transactions.
Prerequisites
Before you begin, ensure you have the following installed on your system:
- Go Ethereum (Geth) library
- Dotenv package (optional but recommended for security)
To install these dependencies, run the appropriate commands for your operating system. For macOS users with Homebrew, you can use:
brew tap ethereum/ethereum
brew install ethereumProject Setup
Start by creating a new Go project and importing the necessary libraries. You'll need to connect to an Ethereum node—we recommend using a service like Infura for reliable access.
Create a .env file to store your sensitive information securely:
INFURA_PROJECT_ID=your_infura_project_id_here
PRIVATE_KEY=your_wallet_private_key_hereAlways use environment variables for sensitive data rather than hardcoding it into your application. This practice prevents accidental exposure of your private keys.
Initial Code Structure
Here's the basic structure to get started:
package main
import (
"context"
"fmt"
"log"
"math/big"
"os"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// Connect to Ethereum node
client, err := ethclient.Dial(fmt.Sprintf("https://goerli.infura.io/v3/%s", os.Getenv("INFURA_PROJECT_ID")))
if err != nil {
log.Fatal(err)
}
}Understanding ERC-20 Token Transfers
Unlike native ETH transfers, sending ERC-20 tokens requires interacting with smart contracts. Each token has its own contract address and Application Binary Interface (ABI), which defines how to interact with it.
The key difference is that instead of sending value directly to an address, you're calling a function on the token's contract that updates its internal accounting system.
Sending ERC-20 Tokens
Let's use UNI tokens as an example for this tutorial. To send these tokens, you'll need:
- The token contract address
- The contract's ABI
- Proper transaction parameters
Obtaining the Contract ABI
Find your token's contract address and locate its ABI on Etherscan:
- Search for the token contract on Etherscan
- Navigate to the "Contract" tab
- Copy the ABI JSON
- Save it as
abi.jsonin your project directory
Token Transfer Implementation
With the ABI available, you can create a bound contract instance:
// Load token contract address
tokenAddress := common.HexToAddress("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984")
// Create contract instance
contract, err := bind.NewBoundContract(tokenAddress, abi, client, client, client)
if err != nil {
log.Fatal(err)
}Building the Transaction
Next, prepare your transaction parameters following EIP-1559 standards:
// Get current nonce
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
// Get suggested gas price
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
// Prepare transaction authentication
auth := bind.NewKeyedTransactor(privateKey)
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // Zero for token transfers
auth.GasLimit = uint64(300000) // Adjust based on contract complexity
auth.GasPrice = gasPrice
// Set recipient and amount
toAddress := common.HexToAddress("RECIPIENT_ADDRESS_HERE")
amount := big.NewInt(1000000000000000000) // 1 token (adjust decimal places)
// Execute transfer
tx, err := contract.Transfer(auth, toAddress, amount)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transaction Hash: %s", tx.Hash().Hex())Complete Code Example
Here's the full implementation for sending ERC-20 tokens:
package main
import (
"context"
"fmt"
"log"
"math/big"
"os"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// Connect to Ethereum node
client, err := ethclient.Dial(fmt.Sprintf("https://goerli.infura.io/v3/%s", os.Getenv("INFURA_PROJECT_ID")))
if err != nil {
log.Fatal(err)
}
// Load token contract
tokenAddress := common.HexToAddress("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984")
contract, err := bind.NewBoundContract(tokenAddress, abi, client, client, client)
if err != nil {
log.Fatal(err)
}
// Prepare transaction parameters
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
auth := bind.NewKeyedTransactor(privateKey)
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0)
auth.GasLimit = uint64(300000)
auth.GasPrice = gasPrice
toAddress := common.HexToAddress("RECIPIENT_ADDRESS_HERE")
amount := big.NewInt(1000000000000000000)
// Execute transfer
tx, err := contract.Transfer(auth, toAddress, amount)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transaction Hash: %s", tx.Hash().Hex())
}Best Practices for Token Transfers
When working with ERC-20 tokens, follow these security and efficiency guidelines:
- Always test transactions on a testnet before using mainnet
- Use proper error handling and validation
- Implement gas estimation to avoid overpaying for transactions
- Keep your private keys secure using environment variables
- 👉 Explore advanced blockchain development strategies
Frequently Asked Questions
What is the difference between sending ETH and ERC-20 tokens?
Sending ETH involves a direct value transfer between addresses, while ERC-20 token transfers require interacting with a smart contract. The contract updates its internal balance mapping to reflect the transfer between addresses.
Why do I need a contract ABI for token transfers?
The Application Binary Interface (ABI) defines how to interact with a smart contract. It specifies the functions available, their parameters, and return types. Without the ABI, your code wouldn't know how to properly call the transfer function.
How can I estimate the correct gas limit for token transfers?
You can use the EstimateGas method provided by most Ethereum clients. Alternatively, check the token contract on Etherscan to see typical gas costs for transfers, then add a buffer for safety.
What are the security risks when sending ERC-20 tokens?
The main risks include exposing private keys, sending to wrong addresses, and contract vulnerabilities. Always use environment variables for sensitive data, double-check addresses, and research tokens before interacting with them.
Can I use this same approach for other Ethereum-compatible blockchains?
Yes, the same basic approach works for any EVM-compatible blockchain. You'll just need to connect to the appropriate RPC endpoint and ensure you're using the correct token contract address for that network.
What should I do if my token transfer fails?
First, check the transaction hash on a block explorer to see the error message. Common issues include insufficient gas, insufficient token balance, or token contract restrictions. Adjust your parameters accordingly and try again.
Remember to always verify your code and test transactions on testnets before deploying on mainnet. 👉 Get real-time tools for transaction monitoring