DEV Community 👩‍💻👨‍💻

Neelansh Mathur
Neelansh Mathur

Posted on

Decentralized Social Network, Subgraphs, IPFS - Polygon Week 4

Part of Polygon Fellowship 2022.


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:

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:

  1. Accept image, title, description, etc. and store this metadata on IPFS
  2. Store the IPFS URLs of posts in a Smart Contract per user, maybe in an array
  3. Query this array of urls to get user's posts
  4. (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!

  1. ERC1155 ProfileHub creates a ProfileNFT for a user (Token + another ERC1155 contract)
  2. Store user's post image on IPFS
  3. Store user's post metadata on IPFS with the image URL from previous step. Metadata is according to the OpenSea Standards.
  4. ERC1155 ProfileNFT's Mint function can be called by the user with the IPFS URL of the NFT as argument.
  5. 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.

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.


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: "",
            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) {
            error: e
Enter fullscreen mode Exit fullscreen mode

Here, the BASE_GATEWAY is

Welp, many more things to come. Especially the Social Media Dapp, an idea I plan to expand upon in the future.


Top comments (0)

👀 Just want to lurk?

That's fine, you can still create an account and turn on features like 🌚 dark mode.