Web3 & Blockchain Introduction
Introduction
Web3 represents the decentralized internet built on blockchain technology. This guide covers blockchain fundamentals, Ethereum, smart contracts with Solidity, Web3.js integration, MetaMask wallet connection, and building decentralized applications (dApps).
1. Blockchain Fundamentals
# Blockchain Core Concepts
1. **Decentralization**
- No central authority
- Distributed across network nodes
- Consensus mechanisms (Proof of Work, Proof of Stake)
2. **Immutability**
- Data cannot be changed once written
- Cryptographic hashing ensures integrity
- Transparent transaction history
3. **Transparency**
- All transactions publicly visible
- Anyone can verify blockchain state
- Pseudonymous addresses
4. **Smart Contracts**
- Self-executing code on blockchain
- Automated agreements without intermediaries
- Trustless execution
# Key Terminology
- **Wallet**: Store and manage cryptocurrency
- **Address**: Public identifier (like email)
- **Private Key**: Secret key to control wallet (like password)
- **Gas**: Transaction fee on Ethereum
- **Wei**: Smallest ETH unit (1 ETH = 10^18 Wei)
- **Gwei**: Common gas price unit (1 Gwei = 10^9 Wei)
- **Block**: Container of transactions
- **Mining/Validation**: Process of adding blocks
- **Consensus**: Agreement on blockchain state
- **Token**: Digital asset on blockchain
- **NFT**: Non-Fungible Token (unique digital asset)
# Ethereum Networks
1. **Mainnet**: Production network (real ETH)
2. **Sepolia**: Test network (free test ETH)
3. **Goerli**: Test network (being deprecated)
4. **Local**: Hardhat/Ganache for development
2. Smart Contract Development (Solidity)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Simple storage contract
contract SimpleStorage {
uint256 private storedData;
address public owner;
event DataStored(uint256 data, address indexed setter);
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this");
_;
}
constructor() {
owner = msg.sender;
}
function set(uint256 x) public {
storedData = x;
emit DataStored(x, msg.sender);
}
function get() public view returns (uint256) {
return storedData;
}
function reset() public onlyOwner {
storedData = 0;
}
}
// ERC-20 Token (simplified)
contract MyToken {
string public name = "MyToken";
string public symbol = "MTK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 initialSupply) {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
}
function transfer(address to, uint256 value) public returns (bool) {
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) public returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public returns (bool) {
require(value <= balanceOf[from], "Insufficient balance");
require(value <= allowance[from][msg.sender], "Allowance exceeded");
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
// NFT Contract (ERC-721 simplified)
contract SimpleNFT {
string public name = "SimpleNFT";
string public symbol = "SNFT";
uint256 private tokenIdCounter;
mapping(uint256 => address) public ownerOf;
mapping(address => uint256) public balanceOf;
mapping(uint256 => string) public tokenURI;
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
function mint(address to, string memory uri) public returns (uint256) {
uint256 tokenId = tokenIdCounter++;
ownerOf[tokenId] = to;
balanceOf[to]++;
tokenURI[tokenId] = uri;
emit Transfer(address(0), to, tokenId);
return tokenId;
}
function transfer(address to, uint256 tokenId) public {
require(ownerOf[tokenId] == msg.sender, "Not token owner");
balanceOf[msg.sender]--;
balanceOf[to]++;
ownerOf[tokenId] = to;
emit Transfer(msg.sender, to, tokenId);
}
}
// Hardhat development setup
// Installation
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
// Initialize Hardhat
npx hardhat init
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,
accounts: [PRIVATE_KEY]
}
},
etherscan: {
apiKey: ETHERSCAN_API_KEY
}
};
// Compile contracts
npx hardhat compile
// Deploy script (scripts/deploy.js)
const hre = require("hardhat");
async function main() {
const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
const contract = await SimpleStorage.deploy();
await contract.waitForDeployment();
console.log("Contract deployed to:", await contract.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
// Deploy
npx hardhat run scripts/deploy.js --network sepolia
// Testing
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("SimpleStorage", function () {
it("Should store and retrieve value", async function () {
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
const contract = await SimpleStorage.deploy();
await contract.set(42);
expect(await contract.get()).to.equal(42);
});
});
3. Web3.js Integration
// Installation
npm install web3
// Connect to Ethereum node
const Web3 = require('web3');
// Using Infura
const web3 = new Web3(`https://mainnet.infura.io/v3/${INFURA_API_KEY}`);
// Using local node
const web3Local = new Web3('http://localhost:8545');
// Check connection
const isConnected = await web3.eth.net.isListening();
console.log('Connected:', isConnected);
// Get network ID
const networkId = await web3.eth.net.getId();
console.log('Network ID:', networkId);
// Get latest block
const block = await web3.eth.getBlock('latest');
console.log('Latest block:', block.number);
// Get account balance
const balance = await web3.eth.getBalance('0xAddress...');
const balanceInEth = web3.utils.fromWei(balance, 'ether');
console.log('Balance:', balanceInEth, 'ETH');
// Send transaction
const account = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY);
web3.eth.accounts.wallet.add(account);
const tx = await web3.eth.sendTransaction({
from: account.address,
to: '0xRecipient...',
value: web3.utils.toWei('0.1', 'ether'),
gas: 21000
});
console.log('Transaction hash:', tx.transactionHash);
// Interact with smart contract
const contractABI = [ /* ABI array from compilation */ ];
const contractAddress = '0xContract...';
const contract = new web3.eth.Contract(contractABI, contractAddress);
// Read from contract (free, doesn't require gas)
const storedValue = await contract.methods.get().call();
console.log('Stored value:', storedValue);
// Write to contract (requires gas)
const receipt = await contract.methods.set(42).send({
from: account.address,
gas: 100000
});
console.log('Transaction receipt:', receipt);
// Listen to events
contract.events.DataStored({
fromBlock: 'latest'
}, (error, event) => {
if (error) console.error(error);
console.log('Data stored:', event.returnValues.data);
});
// Get past events
const events = await contract.getPastEvents('DataStored', {
fromBlock: 0,
toBlock: 'latest'
});
console.log('Past events:', events);
4. MetaMask Integration
// Detect MetaMask
if (typeof window.ethereum !== 'undefined') {
console.log('MetaMask is installed!');
}
// Connect to MetaMask
async function connectWallet() {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
const account = accounts[0];
console.log('Connected account:', account);
return account;
} catch (error) {
console.error('User rejected connection:', error);
}
}
// Get current account
async function getCurrentAccount() {
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
return accounts[0];
}
// React component for wallet connection
import { useState, useEffect } from 'react';
import Web3 from 'web3';
function WalletConnect() {
const [account, setAccount] = useState(null);
const [balance, setBalance] = useState('0');
const [web3, setWeb3] = useState(null);
useEffect(() => {
if (window.ethereum) {
const web3Instance = new Web3(window.ethereum);
setWeb3(web3Instance);
// Listen for account changes
window.ethereum.on('accountsChanged', (accounts) => {
setAccount(accounts[0] || null);
});
// Listen for network changes
window.ethereum.on('chainChanged', () => {
window.location.reload();
});
}
}, []);
const connectWallet = async () => {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
setAccount(accounts[0]);
// Get balance
const balance = await web3.eth.getBalance(accounts[0]);
const balanceInEth = web3.utils.fromWei(balance, 'ether');
setBalance(balanceInEth);
} catch (error) {
console.error('Connection error:', error);
}
};
const disconnectWallet = () => {
setAccount(null);
setBalance('0');
};
return (
{!account ? (
) : (
Account: {account.slice(0, 6)}...{account.slice(-4)}
Balance: {parseFloat(balance).toFixed(4)} ETH
)}
);
}
// Switch network
async function switchNetwork(chainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${chainId.toString(16)}` }],
});
} catch (error) {
// Network doesn't exist, add it
if (error.code === 4902) {
await addNetwork(chainId);
}
}
}
async function addNetwork(chainId) {
const networks = {
11155111: { // Sepolia
chainId: '0xaa36a7',
chainName: 'Sepolia Test Network',
rpcUrls: ['https://sepolia.infura.io/v3/'],
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
blockExplorerUrls: ['https://sepolia.etherscan.io']
}
};
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [networks[chainId]],
});
}
// Sign message
async function signMessage(message, account) {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, account],
});
return signature;
}
// Verify signature
function verifySignature(message, signature, address) {
const web3 = new Web3(window.ethereum);
const signer = web3.eth.accounts.recover(message, signature);
return signer.toLowerCase() === address.toLowerCase();
}
5. Building a Simple dApp
// React dApp for SimpleStorage contract
import { useState, useEffect } from 'react';
import Web3 from 'web3';
const CONTRACT_ADDRESS = '0xYourContractAddress';
const CONTRACT_ABI = [
{
"inputs": [],
"name": "get",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "x", "type": "uint256" }],
"name": "set",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "uint256", "name": "data", "type": "uint256" },
{ "indexed": true, "internalType": "address", "name": "setter", "type": "address" }
],
"name": "DataStored",
"type": "event"
}
];
function StorageDApp() {
const [web3, setWeb3] = useState(null);
const [account, setAccount] = useState(null);
const [contract, setContract] = useState(null);
const [storedValue, setStoredValue] = useState('0');
const [inputValue, setInputValue] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
initWeb3();
}, []);
const initWeb3 = async () => {
if (window.ethereum) {
const web3Instance = new Web3(window.ethereum);
setWeb3(web3Instance);
const contractInstance = new web3Instance.eth.Contract(
CONTRACT_ABI,
CONTRACT_ADDRESS
);
setContract(contractInstance);
// Listen to events
contractInstance.events.DataStored({
fromBlock: 'latest'
}, (error, event) => {
if (!error) {
console.log('Data stored event:', event.returnValues);
fetchStoredValue();
}
});
}
};
const connectWallet = async () => {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
setAccount(accounts[0]);
fetchStoredValue();
};
const fetchStoredValue = async () => {
if (contract) {
const value = await contract.methods.get().call();
setStoredValue(value.toString());
}
};
const storeValue = async () => {
if (!contract || !account || !inputValue) return;
setLoading(true);
try {
await contract.methods.set(inputValue).send({
from: account,
gas: 100000
});
setInputValue('');
await fetchStoredValue();
} catch (error) {
console.error('Transaction error:', error);
alert('Transaction failed: ' + error.message);
} finally {
setLoading(false);
}
};
return (
Simple Storage dApp
{!account ? (
) : (
Connected: {account.slice(0, 6)}...{account.slice(-4)}
Stored Value: {storedValue}
setInputValue(e.target.value)}
placeholder="Enter value"
disabled={loading}
/>
)}
);
}
export default StorageDApp;
6. IPFS for Decentralized Storage
// IPFS for storing NFT metadata and files
// Installation
npm install ipfs-http-client
// Upload to IPFS
import { create } from 'ipfs-http-client';
const client = create({ url: 'https://ipfs.infura.io:5001/api/v0' });
async function uploadToIPFS(file) {
try {
const added = await client.add(file);
const url = `https://ipfs.io/ipfs/${added.path}`;
console.log('IPFS URL:', url);
return url;
} catch (error) {
console.error('IPFS upload error:', error);
}
}
// Upload NFT metadata
async function uploadNFTMetadata(name, description, imageUrl) {
const metadata = {
name,
description,
image: imageUrl,
attributes: [
{ trait_type: 'Level', value: 5 },
{ trait_type: 'Rarity', value: 'Rare' }
]
};
const metadataJSON = JSON.stringify(metadata);
const added = await client.add(metadataJSON);
return `https://ipfs.io/ipfs/${added.path}`;
}
// React component for file upload
function IPFSUpload() {
const [fileUrl, setFileUrl] = useState('');
const [uploading, setUploading] = useState(false);
const handleUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
setUploading(true);
try {
const url = await uploadToIPFS(file);
setFileUrl(url);
} catch (error) {
alert('Upload failed: ' + error.message);
} finally {
setUploading(false);
}
};
return (
{uploading && Uploading to IPFS...
}
{fileUrl && (
Uploaded!
View on IPFS
)}
);
}
// Alternative: Use Pinata for pinning
// https://www.pinata.cloud/
const pinataApiKey = 'your_api_key';
const pinataSecretApiKey = 'your_secret_key';
async function uploadToPinata(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
method: 'POST',
headers: {
'pinata_api_key': pinataApiKey,
'pinata_secret_api_key': pinataSecretApiKey
},
body: formData
});
const data = await response.json();
return `https://gateway.pinata.cloud/ipfs/${data.IpfsHash}`;
}
7. Best Practices
- ✓ Always test on testnets before mainnet deployment
- ✓ Never expose private keys in code or version control
- ✓ Use environment variables for sensitive data
- ✓ Audit smart contracts before production
- ✓ Implement access control in smart contracts
- ✓ Handle wallet connection errors gracefully
- ✓ Validate user inputs before transactions
- ✓ Estimate gas before sending transactions
- ✓ Use events for off-chain data indexing
- ✓ Implement reentrancy guards in contracts
- ✓ Test edge cases and error conditions
- ✓ Keep smart contracts simple and modular
- ✓ Use OpenZeppelin contracts for standards
- ✓ Monitor gas prices for optimal transaction timing
- ✓ Provide clear transaction feedback to users
Conclusion
Web3 and blockchain enable decentralized applications without intermediaries. Master smart contract development with Solidity, integrate Web3.js for blockchain interaction, connect MetaMask wallets, and deploy to Ethereum networks. Use IPFS for decentralized storage and follow security best practices. The Web3 ecosystem is rapidly evolving with new tools and opportunities.