When storing data on the Ethereum blockchain, each computation task has a cost (in gas unit) and you usually want to reduce as much as possible the cost of the transaction for your application.
You want to store an IPFS hash (or Multihash) (or CID) like QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u in a smart contract
First: Read this to understand multihash
Here is three solutions to store an IPFS hash with the gas cost associated.
Assuming gasPrice: 20 000 000 000 (wei)
1. Store it as a String
Smart contract:
contract IPFSStorage {
string hash;
function storeCIDAsString(string _hash) public {
hash = _hash;
}
}
Truffle Test:
it('should store the IPFS CID as a string', async () => {
instance.storeCIDAsString(cid, {'from': accounts[0]}).then(function(txReceipt) {
console.log('# should store the IPFS CID as a string');
let gasUsed = txReceipt.receipt.gasUsed;
console.log("gasUsed: " + gasUsed + " units");
let gasCost = gasUsed*gasPrice;
console.log("gasCost (wei): " + gasCost + " wei");
let gasCostEth = web3.fromWei(gasCost, 'ether')
console.log("gasCost (ether): " + gasCostEth + " ether");
}).catch(function (error) {
console.log(error);
});
});
Result:
# should store the IPFS CID as a string
gasUsed: 85986 units
gasCost (wei): 1719720000000000 wei
gasCost (ether): 0.00171972 ether
2. Store it as a struct
Smart contract:
contract IPFSStorage {
struct Multihash {
bytes32 hash;
bytes2 hash_function;
uint8 size;
}
Multihash multihash;
function storeCIDAsStruct(bytes32 _hash, bytes2 _hash_function, uint8 _size) public {
Multihash memory multihashMemory;
multihash.hash = _hash;
multihash.hash_function = _hash_function;
multihash.size = _size;
multihash = multihashMemory;
}
}
Truffle Test:
it('should store the IPFS CID as a struct', async () => {
let mh = multihashes.fromB58String(Buffer.from(cid))
let args = {
hashFunction: '0x' + mh.slice(0, 2).toString('hex'),
digest: '0x' + mh.slice(2).toString('hex'),
size: mh.length - 2
}
instance.storeCIDAsStruct(args.digest, args.hashFunction, args.size, {'from': accounts[0]}).then(function(txReceipt) {
console.log('# should store the IPFS CID as a struct');
let gasUsed = txReceipt.receipt.gasUsed;
console.log("gasUsed: " + gasUsed + " units");
let gasCost = gasUsed*gasPrice;
console.log("gasCost (wei): " + gasCost + " wei");
let gasCostEth = web3.fromWei(gasCost, 'ether')
console.log("gasCost (ether): " + gasCostEth + " ether");
}).catch(function (error) {
console.log(error);
});
});
Result:
# should store the IPFS CID as a struct
gasUsed: 55600 units
gasCost (wei): 1112000000000000 wei
gasCost (ether): 0.001112 ether
3. Store it in the event log
Smart contract:
contract IPFSStorage {
event CIDStoredInTheLog(string _hash);
function storeCIDInTheLog(string _hash) public {
emit CIDStoredInTheLog(_hash);
}
}
Truffle Test:
it('should store the IPFS CID in the logs', async () => {
instance.storeCIDInTheLog(cid, {'from': accounts[0]}).then(function(txReceipt) {
console.log('# should store the IPFS CID in the logs');
let gasUsed = txReceipt.receipt.gasUsed;
console.log("gasUsed: " + gasUsed + " units");
let gasCost = gasUsed*gasPrice;
console.log("gasCost (wei): " + gasCost + " wei");
let gasCostEth = web3.fromWei(gasCost, 'ether')
console.log("gasCost (ether): " + gasCostEth + " ether");
}).catch(function (error) {
console.log(error);
});
});
Result:
# should store the IPFS CID in the logs
gasUsed: 27501 units
gasCost (wei): 550020000000000 wei
gasCost (ether): 0.00055002 ether
As you can see, each solution work, the only difference is the gas cost of the transaction for storing an IPFS hash in the Blockchain.
You can find the code here (Truffle project)
Best Answer
Yes, you're right. Saving entire image in eth is very costly. I'll suggest you to check off chain data storages like IPFS or Swarm (the ethereum community usually recommends it).
There are alternative APIs are available. Mentioned are two popular services and most of dev's are using. Both are distributed off chain storages.
I'll suggest you to check below link once.
Coming to your question. This is an example of how to store a reference to an image, stored in IPFS, in an ethereum smart contract.
Above code is for just illustration. Modify the data structure as per your requirements. I have not added any security checks.
The idea is to first upload the image to ipfs/swarm/any other off chain data store, get the value calculate hash of image to contract. Download data(image) from offchain and calculate hash and check hash saved in contract.
I feel like the above solution is one of the best way to handle images, because all data is distributed using serverless architecture.