Session 04.03
Integration

ETHEREUM
+ 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