Introduction
Decentralization is the future of data storage and sharing. In this blog, we will explore how to build a decentralized image-sharing system using IPFS (InterPlanetary File System) for storage and Ethereum smart contracts for ownership tracking. We will also go through the mistakes made during the process and how they were fixed.
This Blog writing is only for own use (I written this for only me to remember what i did in free time)
Reach me at github @guider23
What We Built
We created a smart contract that:
- Allows users to upload an image to IPFS and store its hash on the blockchain.
- Tracks image ownership by linking an Ethereum address to each image hash.
- Provides a function to retrieve all stored images along with their owners.
By the end of this guide, you’ll understand how to integrate IPFS with Ethereum smart contracts and interact with them using Hardhat and Ethers.js.
1. Setting Up the Project
Install Dependencies
First, install Hardhat and the required packages:
mkdir blockchain-image-sharing
cd blockchain-image-sharing
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox ethers dotenv
Initialize Hardhat
Run Hardhat setup:
npx hardhat
Select “Create a basic sample project” and follow the prompts.
Install IPFS
We will use local IPFS for testing:
npm install ipfs-http-client
Start a local IPFS node by running:
ipfs daemon
2. Writing the Smart Contract
Create a new file contracts/ImageStorage.sol and add the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ImageStorage {
mapping(string => address) public imageOwners;
string[] public imageHashes;
event ImageUploaded(string ipfsHash, address owner);
function uploadFile(string memory _hash) public {
require(imageOwners[_hash] == address(0), "Image already uploaded!");
imageOwners[_hash] = msg.sender;
imageHashes.push(_hash);
emit ImageUploaded(_hash, msg.sender);
}
function getImageOwner(string memory _hash) public view returns (address) {
return imageOwners[_hash];
}
function getImages() public view returns (string[] memory) {
return imageHashes;
}
}
This contract does the following:
- Stores IPFS hashes mapped to Ethereum addresses.
- Prevents duplicate uploads.
- Emits an event when an image is uploaded.
- Retrieves all stored images and owners.
3. Deploying the Smart Contract
Create a scripts/deploy.js file:
const hre = require("hardhat");
async function main() {
const ImageStorage = await hre.ethers.getContractFactory("ImageStorage");
const contract = await ImageStorage.deploy();
await contract.waitForDeployment();
console.log("Contract deployed to:", await contract.getAddress());
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Run the Hardhat local blockchain:
npx hardhat node
Deploy the contract:
npx hardhat run scripts/deploy.js --network localhost
You should see an output like:
Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
4. Interacting with the Smart Contract
Open Hardhat Console
npx hardhat console --network localhost
Connect to the Contract
const contract = await ethers.getContractAt("ImageStorage", "0x5FbDB2315678afecb367f032d93F642f64180aa3");
Upload an Image Hash
await contract.uploadFile("QmfJwqRTWPp4Ag4j2tzcUWBcYZZ3JHoEdzpmfFyRx1aZ4a");
Retrieve Uploaded Images
const images = await contract.getImages();
console.log(images);
Expected Output:
['QmfJwqRTWPp4Ag4j2tzcUWBcYZZ3JHoEdzpmfFyRx1aZ4a']
Get Image Owner
const owner = await contract.getImageOwner("QmfJwqRTWPp4Ag4j2tzcUWBcYZZ3JHoEdzpmfFyRx1aZ4a");
console.log("Owner Address:", owner);
Expected Output:
Owner Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
const imageList = await contract.getImages();
console.log(imageList);
//Get a array of stored hashes
5. Common Mistakes and Fixes
1. Function Not Found Error
Issue:
Uncaught TypeError: contract.getFiles is not a function
Fix: Ensure the function name matches exactly as defined in Solidity (getImages instead of getFiles).
2. “Contract Not Defined” Error
Issue:
Uncaught ReferenceError: contract is not defined
Fix: Ensure you have correctly initialized the contract instance before calling functions.
3. Duplicate Upload Prevention
Issue: Uploading the same image hash again gives an error. Fix: This is intentional to prevent duplicate storage on the blockchain.
6. Next Steps
Now that we have a working decentralized image-sharing system, we can:
- Build a Frontend using React.js to interact with the contract.
- Implement User Authentication using MetaMask.
- Deploy the Contract to a Public Testnet like Goerli or Sepolia.
Stay tuned for the next part where we integrate this with a frontend!