Interacting with smart contracts on the Ethereum blockchain is a foundational skill for any Web3 developer. Whether you're minting NFTs, transferring tokens, or executing complex decentralized logic, understanding how transactions work under the hood is essential. This guide breaks down the mechanics of Ethereum transactions, explores modern transaction formats like EIP-1559, and demonstrates practical implementation using tools like Ethers.js.
Understanding Ethereum Transactions
At its core, an Ethereum transaction is a signed data package that represents a message sent from one account to another—either externally owned accounts (EOAs) or smart contracts. These transactions can transfer ETH, trigger contract functions, or even deploy new contracts.
Each transaction includes several critical fields:
- Nonce: Ensures each transaction is processed only once per sender.
- Gas Price & Gas Limit: Define the maximum amount of gas and the price per unit the sender is willing to pay.
- To: The recipient address (or contract address).
- Value: The amount of ETH to send.
- Data: Optional field used to pass input data when interacting with smart contracts.
- v, r, s: Cryptographic signatures proving ownership of the sending account.
👉 Learn how to securely manage blockchain interactions with trusted tools.
Before EIP-1559, users set a flat gasPrice. Now, the fee market has evolved into a more predictable structure.
EIP-1559: The Modern Transaction Format
Introduced in 2021, EIP-1559 revolutionized Ethereum’s fee mechanism by introducing a dynamic base fee and a tip (priority fee) for miners or validators. This update significantly improves user experience by reducing volatility in gas pricing.
Key components of EIP-1559 transactions:
- maxFeePerGas: The total maximum you’re willing to pay per gas unit, including both base and priority fees.
- maxPriorityFeePerGas: The extra tip offered to validators to prioritize your transaction.
- gasLimit: The maximum amount of gas you’re willing to consume.
- accessList (optional): Predefined list of addresses and storage keys the transaction will access—helps reduce gas costs.
- chainId: Identifies the network (e.g., Ethereum Mainnet = 1). Use resources like ChainList to verify chain IDs.
This format allows wallets and developers to estimate fees more accurately and ensures smoother transaction inclusion during network congestion.
Three Types of Ethereum Transactions
1. ETH Transfer
The simplest form of transaction—sending ETH from one wallet to another.
{
"from": "0x17eeeeeeeeeeeeee",
"to": "0x302222222333333333",
"value": "1000000000000000000",
"data": "0x",
"gasLimit": "21000"
}Note: The data field is empty (0x) because no contract logic is being executed.
2. Smart Contract Interaction
When calling a function in a deployed contract, the data field contains encoded function calls and parameters.
{
"from": "0x17eeeeeeeeeeeeee",
"to": "0x302222222333333333",
"value": "10000000000000000",
"data": "0xaabbccddeeff...",
"gasLimit": "300000"
}The data payload is generated by hashing the function signature using Keccak-256, then appending ABI-encoded arguments. For example, calling mint(address,uint256) generates a selector like 0x448c....
👉 Discover how to interact with smart contracts safely and efficiently.
3. Contract Deployment
A special case where the to field is omitted, and the data field contains the full bytecode of the contract to be deployed.
Real-World On-Chain Example: NFT Minting
Let’s analyze a real smart contract used for minting NFTs. This example implements a blind box NFT drop with a capped supply and payable mint function.
Sample Solidity Contract
pragma solidity >=0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
using Strings for uint256;
address owner;
uint256 public maxSupply = 10;
bool private isOpened = false;
uint256 public counter = 0;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {
owner = msg.sender;
}
function openBlindBox() external onlyOwner {
isOpened = true;
}
function _baseURI() internal pure override returns (string memory) {
return "ipfs://QmXxZBg4RnGxC2dDxfUSAmxgGooHsoncPQgCLiNw8kj3Ls/";
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!isOpened) {
return _baseURI();
}
return string(abi.encodePacked("ipfs://QmWQcaFFCm9ofyVN2ZwGbTGopLEbQ6QSc2Xn1C7ekKAYDF/", tokenId.toString(), ".json"));
}
function mint(address to, uint256 amount) external payable {
require(amount + counter <= maxSupply, "over max supply.");
require(amount * 10000000000000000 == msg.value, "balance error!");
for (uint256 i = 0; i < amount; i++) {
_mint(to, counter);
counter++;
}
}
}This contract allows:
- Only the owner to reveal metadata (
openBlindBox) - Public minting with ETH payment
- IPFS-based metadata hosting
Tools for On-Chain Interaction
To interact programmatically with Ethereum, developers use libraries that abstract low-level RPC calls.
Popular Web3 Libraries
- Web3.js: One of the earliest JavaScript libraries; widely adopted but heavier.
- Ethers.js: Lightweight, modular, and beginner-friendly. Preferred for modern dApps.
- Viem: A newer TypeScript-first library built for performance and type safety.
Connecting via RPC Providers
To communicate with the Ethereum network, you need an RPC endpoint. Reliable providers include:
- Infura
- Alchemy
- BlastAPI
These services offer scalable access to blockchain data without running your own node.
Practical Guide: Interact with Smart Contracts Using Ethers.js
Here’s a complete script to mint NFTs from the above contract using Ethers.js.
Setup Environment Variables
RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
PRIVATE_KEY=your_wallet_private_key_hereNode.js Script
import { ethers } from "ethers";
import dotenv from 'dotenv';
dotenv.config();
const RPC_URL = process.env.RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const main = async () => {
if (!RPC_URL) throw new Error("RPC_URL not set.");
if (!PRIVATE_KEY) throw new Error("PRIVATE_KEY not set.");
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const contract_address = "0x9c6471C2a6099993299Ca7E1a7E5a22D1F26902C";
const nft_abi = '[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"}, ...]'; // Full ABI truncated for brevity
const contract = new ethers.Contract(contract_address, nft_abi, signer);
try {
const res = await contract.mint(
"0x3D71D7DC971e9f8405f287A340E8f65a7a1d392a",
3,
{ value: ethers.utils.parseEther("0.03") }
);
console.log("Transaction hash:", res.hash);
await res.wait();
console.log("Mint successful!");
} catch (error) {
console.error("Error minting NFT:", error.message);
}
};
main();This script connects to the network, signs the transaction locally, and submits it to mint three NFTs with a payable value of 0.03 ETH.
Frequently Asked Questions
What is the difference between gasPrice and maxFeePerGas?
gasPrice was used in legacy transactions as a fixed price per gas unit. In EIP-1559, maxFeePerGas sets the upper limit you’re willing to pay, while the actual cost adjusts based on network demand plus a tip.
How do I generate the data field for contract calls?
Use libraries like Ethers.js or Web3.js to encode function names and arguments. For example: contract.interface.encodeFunctionData("mint", [address, 3]).
Why does my transaction fail even with high gas?
Common reasons include insufficient ETH balance for value + gas, incorrect ABI, reverted logic inside the contract (e.g., paused mint), or invalid signatures.
Can I interact with contracts without writing code?
Yes—wallets like MetaMask allow manual interaction through “Write Contract” interfaces on Etherscan, provided you have the ABI.
What are fallback and receive functions?
receive(): Triggered when ETH is sent directly to a contract without data.fallback(): Executed when no matching function is found or when receiving ETH with data. Both enable contracts to handle incoming Ether securely.
How do I verify transaction success?
Check the transaction receipt on block explorers like Etherscan. A status of 1 means success; 0 indicates failure.
👉 Start building and testing your own smart contract interactions today.
By mastering these concepts—from transaction anatomy to real-world coding—you gain full control over on-chain operations. Whether you're launching an NFT collection or integrating DeFi protocols, this knowledge forms the backbone of effective blockchain development.