Decentralized Social Network, Subgraphs, IPFS - Polygon Week 4
Neelansh Mathur
Posted on July 10, 2022
Part of Polygon Fellowship 2022.
gm
It's been a crazy week. The craziest, in fact, relative to the previous weeks. This week was all about exploring web3 Infrastructure & Storage using IPFS, Alchemy, Pinata, TheGraph Protocol, etc.
Social Media Dapp
Let's start with the heavy hitter.
The biggest assignment in this week was to build a full-stack Decentralized Social Media App with our own hint of creativity.
The assignment on Polygon Academy is given here: https://academy.polygon.technology/module-8-wizard-challenge/final-asssignment
The statement says:
Submit a full stack social media dapp as your final assignment.
The dapp lets a user post and receive images to an anonymous network of contributors. There will be a feed of all images.
No need to add extra functionality like likes and comments, just basic push-pull data submission. Use ethereum to point to images and store them on IPFS. Use polygon as an L2 for this. Submit your assignment below.
Here is what I think the normal approach will be:
- Accept image, title, description, etc. and store this metadata on IPFS
- Store the IPFS URLs of posts in a Smart Contract per user, maybe in an array
- Query this array of urls to get user's posts
- (Optional) Use Subgraphs or Moralis to index these posts
This works. But I had been thinking about creating Posts as NFTs in some way, inspired by reading about the Lens Protocol.
So, here is my ingenious approach!
- ERC1155 ProfileHub creates a ProfileNFT for a user (Token + another ERC1155 contract)
- Store user's post image on IPFS
- Store user's post metadata on IPFS with the image URL from previous step. Metadata is according to the OpenSea Standards.
- ERC1155 ProfileNFT's Mint function can be called by the user with the IPFS URL of the NFT as argument.
- Indexing is not directly necessary as many services already index NFTs on OpenSea! 🌊
So in this approach, There is a central ProfileHub collection which mints a Profile Token and creates another collection called ProfileNFT for each user.
Flow:
ProfileHub Collection => ProfileNFT Token + ProfileNFT Contract
ProfileNFT Contract = User's Posts as Tokens with Traits
The benefits of this approach is that you can view your posts even on OpenSea! It has all the advantages of a regular NFT.
IPFS
There are multiple ways to upload an image to IPFS. You can do it either manually or programatically.
I decided to create an API using NodeJS and Fastify that accepts user's image and uploads it to IPFS using Pinata's Node SDK.
It seems a bit overkill but I had to create this for my Social Media Dapp anyways so it can be reused now.
A MAJOR issue I had was with taking user's image. Normally, multipart images work with frameworks like Express and Fastify, but here the issue was that even after getting the image, Pinata's SDK wasn't accepting the file Buffer 😵💫
After a LOT of hours of on-and-off debugging, I found out that there is a small hack we need to do to the file's Readable
Stream to make it work with Pinata.
I wonder why nobody has covered this at all. I only found this out from a stackoverflow question that was not that much related to my problem.
The overall code (which I turned into an NFT creation tool for my social dapp later on) uploads an image to IPFS, then uploads the metadata with image url to IPFS to serve as an NFT.
const image = await (req.body as any).image
if (!image) reply.send({ error: "No image file" })
if (!(req.body as any).name.value || !(req.body as any).description.value) reply.send({ error: "Missing name or description fields" })
const readableStream = Readable.from(await image.toBuffer())
// IMPORTANT: This is the only fix for Pinata to work. It needs a 'path' property in the Readable Stream
// @ts-ignore
readableStream.path = image.filename
try {
const imageResult = await pinata.pinFileToIPFS(readableStream)
const imageURL = BASE_GATEWAY + imageResult.IpfsHash
const data = {
name: (req.body as any).name.value,
description: (req.body as any).description.value,
image: imageURL,
external_url: "https://github.com/neelansh15",
attributes: [
{
trait_type: "Likes",
value: 0
},
{
display_type: "date",
trait_type: "Posted on",
value: +new Date / 1000
}
]
}
const result = await pinata.pinJSONToIPFS(data)
const nftURL = BASE_GATEWAY + result.IpfsHash
reply.send({ ...result, url: nftURL })
}
catch (e) {
console.error(e)
reply.send({
error: e
})
}
Here, the BASE_GATEWAY is https://gateway.pinata.cloud/ipfs/
.
Welp, many more things to come. Especially the Social Media Dapp, an idea I plan to expand upon in the future.
gn
Posted on July 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.