Decentralized Social Network, Subgraphs, IPFS - Polygon Week 4
Tags: blockchain, webdev, javascript, ethereum
Published: 2022-07-10 18:22:00

Part of [Polygon Fellowship 2022](https://polygon.technology/polygon-fellowship/).

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:
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](http://lens.xyz/).

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.

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.

```ts

    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