A Practical Guide to Ethereum Transactions using Web3.js and Infura

·

Introduction

Developing automated transaction scripts requires a solid understanding of Ethereum's core concepts and the tools available. This guide walks through the fundamental principles and provides a practical workflow for sending both Ether and ERC-20 tokens using Web3.js and the Infura service.

Core Concepts: Accounts, Gas, and Transactions

An Ethereum account consists of three primary components: a Mnemonic Phrase, a Private Key, and a public Address. The private key and mnemonic phrase are interoperable; the mnemonic is essentially a human-readable representation of the private key, generated from a standardized wordlist for easier backup and storage.

Ethereum operates as a distributed smart contract platform. Its native cryptocurrency, Ether (ETH), is used to pay for transaction fees, commonly known as gas. The platform also supports the creation of other digital assets through the ERC-20 standard, often referred to as tokens.

At the heart of the network is the Ethereum Virtual Machine (EVM), a Turing-complete runtime environment that executes smart contracts. Every operation performed on the EVM consumes gas, which is priced in a unit called Gwei.

The total transaction fee is calculated as: fee = gasUsed * gasPrice.

This fee is then converted from Gwei to Ether for final settlement. It's important to note that the exact gasUsed for a transaction cannot be known beforehand but can be estimated based on historical data.

Understanding Transaction Fees with a Practical Example

Let's break down a real transaction using the formula above. Consider a transaction with the following parameters:

We calculate the fee as follows:

fee = 30,237 * 13 = 393,081 Gwei

Since 1 Ether = 1,000,000,000 Gwei, the fee in Ether is:

393,081 / 1,000,000,000 = 0.000393081 ETH

This matches the "Transaction Fee" field often seen on blockchain explorers, confirming our calculation is correct.

Tools and Libraries for Development

To interact with the Ethereum blockchain programmatically, we use two main tools:

  1. Infura: A reliable API service that provides access to the Ethereum network without needing to run a full node.
  2. Web3.js: A popular JavaScript library that allows you to create, sign, and broadcast transactions.

👉 Explore the official API documentation for these tools

Setting Up Your Development Environment

Step 1: Install Required Dependencies

Begin by installing the necessary Node.js packages. Using specific versions can help avoid compatibility issues.

npm install [email protected] [email protected] [email protected] [email protected]

Step 2: Configure the Infura Provider

Create a file named provider.js to configure Web3 to connect to the Ethereum mainnet via Infura.

const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider');
const provider = { send: createInfuraProvider().sendAsync };
module.exports = provider;

Step 3: Initialize the Web3 Client

Create a client.js file to initialize your Web3 instance with the provider and prepare for contract interactions.

const Web3 = require('web3');
const Transaction = require('ethereumjs-tx');
const provider = require('./provider');
const contractABI = require('./erc20-abi.json'); // Your ERC-20 ABI goes here

const web3 = new Web3(provider);

Deriving Keys and Addresses from a Mnemonic

If you are starting with a mnemonic phrase, you must first derive the private key and Ethereum address. The following function demonstrates this process.

const bip39 = require('bip39');
const hdkey = require('ethereumjs-wallet/hdkey');

function generateAddressesFromSeed(seed, count = 2) {
  const hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(seed));
  const wallet_hdpath = "m/44'/60'/0'/0/";
  const accounts = [];

  for (let i = 0; i < count; i++) {
    let wallet = hdwallet.derivePath(wallet_hdpath + i).getWallet();
    let address = '0x' + wallet.getAddress().toString("hex");
    let privateKey = wallet.getPrivateKey().toString("hex");
    accounts.push({ address: address, privateKey: privateKey });
  }
  return accounts;
}

// Usage
const accounts = generateAddressesFromSeed('your mnemonic phrase here');
const myAddress = accounts[0].address;
const privateKey = Buffer.from(accounts[0].privateKey, 'hex');

Executing an Ether Transfer

Sending Ether involves constructing a transaction object, signing it with your private key, and broadcasting it to the network.

const toAddress = '0x114fdf4ebd30ae646fac486b4e796616c95ca244'; // Recipient address
const amount = web3.utils.toHex(web3.utils.toWei('1', 'ether')); // Amount to send

web3.eth.getTransactionCount(myAddress)
.then(async count => {
  const txParams = {
    from: myAddress,
    gasPrice: web3.utils.toHex(1 * 1e9), // 1 Gwei
    gasLimit: web3.utils.toHex(210000),
    to: toAddress,
    value: amount,
    nonce: web3.utils.toHex(count)
  };

  const tx = new Transaction(txParams);
  tx.sign(privateKey);

  web3.eth.sendSignedTransaction('0x' + tx.serialize().toString('hex'))
  .on('transactionHash', console.log);
})
.catch(err => {
  console.log(err);
});

Executing an ERC-20 Token Transfer

Transferring tokens requires interacting with a smart contract. The key differences are specifying the contract address and encoding the function call in the data field.

const contractAddress = '0x3c76ef53be46ed2e9be224e8f0b92e8acbc24ea0'; // Token contract address
const contract = new web3.eth.Contract(contractABI, contractAddress);
const toAddress = '0x114fdf4ebd30ae646fac486b4e796616c95ca244'; // Recipient address

// Get token decimals and calculate the raw amount to send
const decimals = await contract.methods.decimals().call();
const amount = web3.utils.toHex(1 * (10 ** decimals)); // Send 1 whole token

web3.eth.getTransactionCount(myAddress)
.then(async count => {
  const txParams = {
    from: myAddress,
    gasPrice: web3.utils.toHex(1 * 1e9),
    gasLimit: web3.utils.toHex(210000),
    to: contractAddress, // The token's contract address
    value: web3.utils.toHex(0), // No Ether is sent, only tokens
    data: contract.methods.transfer(toAddress, amount).encodeABI(), // Encoded function call
    nonce: web3.utils.toHex(count)
  };

  const tx = new Transaction(txParams);
  tx.sign(privateKey);

  web3.eth.sendSignedTransaction('0x' + tx.serialize().toString('hex'))
  .on('transactionHash', console.log);
})
.catch(err => {
  console.log(err);
});

Key Transaction Parameters Explained

Estimating Transaction Costs Accurately

Estimating fees correctly is crucial. You can fetch the current recommended gas prices from several sources:

  1. EthGasStation: Provides a rich dataset (https://ethgasstation.info/json/ethgasAPI.json) with fields like fast, fastest, and safeLow (values must be divided by 10 to get the price in Gwei).
  2. Etherchain: Offers a gas price oracle (https://www.etherchain.org/api/gasPriceOracle).
  3. Web3.js: The web3.eth.getGasPrice() method returns an average price in Wei.

For a token transfer, you need to estimate the gas it will consume. You can do this by simulating the call.

const estimategasUsed = await contract.methods.transfer(toAddress, amount).estimateGas({ from: myAddress });
Important: The amount must not exceed your token balance, or the estimation will fail.

To convert the estimated gas cost into a dollar amount, you need the current ETH price. You can fetch this from an API.

// Example using a generic API endpoint to get the ETH/USD price
const exchangeRateResponse = await fetch('https://api.infura.io/v1/ticker/ethusd');
const exchangeData = await exchangeRateResponse.json();
const ethToUsdRate = exchangeData.bid; // Use the 'bid' price

// Calculate total cost in USD
const totalGasCostGwei = estimategasUsed * selectedGasPrice;
const totalGasCostEther = totalGasCostGwei / 1e9;
const totalCostUsd = totalGasCostEther * ethToUsdRate;

Frequently Asked Questions

What is the difference between a private key and a mnemonic phrase?
A private key is a long, complex string of letters and numbers that grants control over an Ethereum account. A mnemonic phrase is a human-readable version of that private key, typically consisting of 12 or 24 common words, making it easier to back up and secure offline.

Why did my transaction fail or get dropped?
Transactions can fail if the gas limit is too low for the computational work required. They can be dropped from the network mempool if the gas price is set too low and the transaction remains unconfirmed for a long time (often超过 10 hours). Always check the status of your transaction and be prepared to resubmit it with a higher gas price if necessary.

How do I find the correct ABI for an ERC-20 token?
The Application Binary Interface (ABI) is a JSON file that defines how to interact with a smart contract. You can often find it on the token's official website, its GitHub repository, or on blockchain explorers like Etherscan by searching for the token's contract address.

What happens if I get the nonce wrong?
The nonce must be exactly equal to the number of transactions you have ever sent from your account. If it is too low, the transaction will be rejected. If it is too high, the transaction will be queued but will not be processed until all previous nonces have been used. It is crucial to always fetch the current transaction count for your account before sending.

Can I cancel a pending transaction?
You cannot cancel a signed transaction, but you can effectively replace it by sending a new transaction with the same nonce and a higher gas price. This incentivizes miners to include the new transaction instead of the old one. This is known as accelerating a transaction.

How are Ethereum blocks different from Bitcoin blocks?
Bitcoin produces a new block approximately every 10 minutes, with a block size limit around 1-4 MB. Ethereum aims for a new block every 12-15 seconds and does not have a strict block size limit; instead, it uses a gas limit per block to constrain computational complexity.

Summary and Key Takeaways

Automating Ethereum transactions requires careful attention to detail regarding gas, nonce management, and the differences between sending Ether and ERC-20 tokens. Using services like Infura simplifies connectivity, while libraries like Web3.js provide the necessary tools for transaction construction and signing.

Remember to always estimate gas costs accurately and monitor transaction status. Transactions with low gas prices may be dropped if the network is congested. By understanding these core concepts and utilizing the provided code examples, you can build robust scripts for handling various types of blockchain transactions.