Session 04.03
IntegrationETHEREUM
+ IPFS
Pair Ethereum’s stateful contracts with IPFS content addressing. Store hashes on-chain, keep bytes off-chain, and build resilient, verifiable Web3 apps.
01
The Storage Trilemma
Ethereum excels at verifiable state but is economically unsuitable for file storage. IPFS complements it by storing content cheaply and verifiably.
NFT Metadata
❌ 500KB JSON on-chain ≈ massive gas
✅ Store CID on-chain (<$2), host file on IPFS
Document Verification
❌ 1MB PDF on-chain = impractical
✅ Hash on-chain, file on IPFS = efficient + verifiable
Media DApps
❌ Video/images on-chain = impossible
✅ CID registry on-chain, media on IPFS = scalable
Ethereum
Ownership, permissions, transactions (expensive, permanent)
IPFS
File storage & distribution (cheap, content-addressed)
Together: immutable references with scalable storage
02
Decentralized Application Architecture
Workflow
1
User uploads file
↓
2
Upload to IPFS (get CID)
↓
3
Store CID in smart contract
↓
4
Emit event indexed by CID
↓
5
Frontend reads CID from contract
↓
6
Fetch file from IPFS via CID
↓
7
Render to user
1
Frontend (Next.js)
- • UI + wallet connect
- • IPFS client integration
- • Reads events & CIDs
2
Smart Contract
- • Store CIDs
- • Manage permissions
- • Track ownership
3
IPFS Network
- • File storage
- • Content distribution
- • Pinning services
4
Ethereum Network
- • State management
- • Transaction execution
- • Immutable records
03
CID Registry Contract
DocumentRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract DocumentRegistry {
event DocumentStored(address indexed owner, string cid, uint256 timestamp);
struct Document {
string cid;
address owner;
uint256 timestamp;
string metadata;
}
mapping(bytes32 => Document) public documents;
mapping(address => bytes32[]) public userDocuments;
function storeDocument(string memory _cid, string memory _metadata) public {
bytes32 docId = keccak256(abi.encodePacked(_cid, msg.sender, block.timestamp));
documents[docId] = Document({
cid: _cid,
owner: msg.sender,
timestamp: block.timestamp,
metadata: _metadata
});
userDocuments[msg.sender].push(docId);
emit DocumentStored(msg.sender, _cid, block.timestamp);
}
function getDocument(bytes32 _docId) public view returns (string memory, address, uint256, string memory) {
Document memory doc = documents[_docId];
return (doc.cid, doc.owner, doc.timestamp, doc.metadata);
}
function getUserDocuments(address _user) public view returns (bytes32[] memory) {
return userDocuments[_user];
}
}CID Storage
Store IPFS CIDs as strings with metadata
Ownership
Each document linked to uploader
Verification
Timestamped records; recover who uploaded what
Events
Indexed events for off-chain indexing
04
Complete DApp Workflow
Upload Flow
import { create } from 'ipfs-http-client'
import { ethers } from 'ethers'
// 1. IPFS
const ipfs = create({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' })
// 2. Ethereum
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const contract = new ethers.Contract(contractAddress, abi, signer)
// 3. Upload
async function uploadFile(file) {
const added = await ipfs.add(file)
const cid = added.path
const tx = await contract.storeDocument(
cid,
JSON.stringify({ name: file.name, type: file.type, size: file.size })
)
await tx.wait()
return cid
}
// 4. Query
async function getUserDocuments() {
const address = await signer.getAddress()
const docIds = await contract.getUserDocuments(address)
const docs = []
for (const docId of docIds) {
const doc = await contract.getDocument(docId)
docs.push({ id: docId, cid: doc.cid, owner: doc.owner, timestamp: doc.timestamp, metadata: JSON.parse(doc.metadata) })
}
return docs
}05
File Upload Component
FileUploader.tsx
import { useState } from 'react'
import { Upload, CheckCircle2, Loader } from 'lucide-react'
export default function FileUploader() {
const [file, setFile] = useState(null)
const [uploading, setUploading] = useState(false)
const [cid, setCid] = useState('')
const [txHash, setTxHash] = useState('')
const handleUpload = async () => {
if (!file) return
setUploading(true)
try {
const added = await ipfs.add(file)
setCid(added.path)
const tx = await contract.storeDocument(added.path, JSON.stringify({ name: file.name }))
const receipt = await tx.wait()
setTxHash(receipt.hash)
} finally {
setUploading(false)
}
}
return (
<div className="p-6 bg-[#0A0A0A] border border-neutral-800 rounded-sm">
<h3 className="text-lg font-bold mb-4">Upload to IPFS + Ethereum</h3>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
<button onClick={handleUpload} disabled={!file || uploading}>
{uploading ? 'Uploading...' : 'Upload File'}
</button>
{cid && <div>{cid}</div>}
{txHash && <div>{txHash}</div>}
</div>
)
}Upload to IPFS + Ethereum (Demo)
06
Production Applications
NFT Platforms
- •Metadata/images on IPFS
- •Token URI stores CID
- •Immutable art + provenance
Document Verification
- •Upload PDFs to IPFS
- •Store hash on-chain
- •Tamper-proof certificates
Decentralized Social
- •Posts/media on IPFS
- •Ownership graph on Ethereum
- •Censorship-resistant feeds
Supply Chain
- •Product specs on IPFS
- •Tracking records on-chain
- •Transparent provenance
Medical Records
- •Encrypted files on IPFS
- •Access control on Ethereum
- •Privacy + portability
Academic Publishing
- •Papers on IPFS
- •Citation network on-chain
- •Permanent scholarly record
07
Production Considerations
Before Launch
- Pin critical content (multi-provider)
- Validate metadata in contracts
- Encrypt private data
- Test retrieval across gateways
- Add robust error handling
- Plan gateway redundancy
Cost Optimization
- Store only CIDs on-chain
- Emit events for indexing instead of storage
- Batch CID updates
- Consider L2s (Polygon, Arbitrum)
User Experience
- Show upload progress
- Provide multiple gateway fallbacks
- Cache retrieved content
- Render human-readable metadata
08
Key Takeaways
Ethereum handles state; IPFS handles storage
Only CIDs go on-chain, not files
Contracts act as CID registries
Events enable off-chain indexing
Pinning required for persistence
Multiple gateways provide resilience
Encrypt sensitive data
This pattern powers most Web3 apps