Skip to content

mugrebot/scaffold-eth-lens-challenge

Repository files navigation

πŸ— Scaffold-ETH + β–² Next.js + Lens Developer Challenge 🌿

Lets BUIDL on Lens! πŸš€

image

πŸ” What is Lens?

Lens Protocol is a decentralized social network that has a low carbon footprint and an established web3 team behind it. Each user retains ownership over their profile and the content they create.

Learn more about Lens here!

πŸ„β€β™‚οΈ Here we go!

Prerequisites: Node plus Yarn and Git

clone/fork πŸ— scaffold-eth:

git clone https://github.com/mugrebot/scaffold-eth-lens-challenge

checkout the scaffold-nextjs branch

cd scaffold-eth-lens-challenge

install and start your πŸ‘·β€ Hardhat chain:

yarn install
yarn chain

in a second terminal window, πŸ›° deploy your contract (once you write them!):

yarn generate 

cd scaffold-eth-lens-challenge

yarn deploy

πŸ” Edit your smart contract YourContract.sol in packages/hardhat/contracts

πŸ’Ό Edit your deployment scripts in packages/hardhat/deploy

πŸ“± Open http://localhost:3000 to see the app

πŸ“š Lens API

In this challenge you'll use the Lens API to fetch and render a social media feed, navigate to view an individual profile, and fetch and view the user's publications.

The Lens API can be tested at any time here using any of the GraphQL queries in the API docs.

Watch this intro video by Nader Dabit on the Lens API.

Intro to Lens API

1. πŸ‘¨πŸ»β€πŸ’» Install dependancies

First, change into the services directory and install these dependencies for the GraphQL client into the packages/services folder:

cd packages/services
yarn add @apollo/client graphql

in a third terminal window, start your πŸ“± frontend:

cd ../../
yarn start

πŸ“ You will edit your frontend app.jsx in packages/react-app/src

2. 🌐 Create the API

Creating a basic GraphQL API is simple, we can do it in just a couple of lines of code. We'll also define the first GraphQL query we'll be using in our app.

Create a new file named api.js in the packages/react-app/src/helpers and add the following code:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const API_URL = "https://api-mumbai.lens.dev";

/* create the API client */
export const client = new ApolloClient({
  uri: API_URL,
  cache: new InMemoryCache(),
});

export const authenticate = gql`
  mutation Authenticate($address: EthereumAddress!, $signature: Signature!) {
    authenticate(request: { address: $address, signature: $signature }) {
      accessToken
      refreshToken
    }
  }
`;

export const createProfile = gql`
  mutation createProfile($request: CreateProfileRequest!) {
    createProfile(request: $request) {
      ... on RelayerResult {
        txHash
      }
      ... on RelayError {
        reason
      }
      __typename
    }
  }
`;

The queries we've defined here will allow a user to authenticate (https://docs.lens.xyz/docs/authentication-quickstart), generating an authentication token that is required to perform other queries such as creating a profile, following other users, setting default handle, changing profile picture, etc. 

3. 😎 Authentication

we can now authenticate using the api, lets add the following code exists in to our app.jsx file, note that the imports should be near the other imports, the state variables with the other state variables, and the functions might be seperated along the app.jsx file as opposed to how you see them below!

import {
  authenticate,
  challenge,
  client,
  exploreProfiles,
  profileaddress,
  getPublications,
  createProfile,
} from "./helpers/api.js";


  const [address, setAddress] = useState();
  const [token, setToken] = useState();
  const [profileId, setProfileId] = useState();
  const [user_selected_handle, setUser_selected_handle] = useState();
  const [open, setOpen] = useState(false);
  const [confirmLoading, setConfirmLoading] = useState(false);
  const [modalText, setModalText] = useState("Content of the modal");
  const [handle, setHandle] = useState();
  const [signer, setUserSigner] = useState();

 async function Login() {
    try {
      /* first request the challenge from the API server */
      const challengeInfo = await client.query({
        query: challenge,
        variables: { address },
      });

      const signature = await signer.signMessage(challengeInfo.data.challenge.text);
      console.log(address);
      /* authenticate the user */
      const authData = await client.mutate({
        mutation: authenticate,
        variables: {
          address,
          signature,
        },
      });
      /* if user authentication is successful, you will receive an accessToken and refreshToken */
      const {
        data: {
          authenticate: { accessToken },
        },
      } = authData;
      console.log({ accessToken });
      setToken(accessToken);
    } catch (err) {
      console.log("Error signing in: ", err);
    }
  }

4. 😎 Claiming a handle!

So far we should be able to "login" and receive an authentication token, it'll be up to you to create the query on app.jsx

Since passing something as a header isn't included in the authentication step, we will give that first part to you - write the rest!

async function createProfileRequest() {
  if (user_selected_handle === undefined) {
    setOpen(true);
    console.log("modal should open");
  } else {
    try {
      //lets package the request we are going to make! it requires a handle that the user will select via modal, profilePictureUri: null, followModule: null, 
      const request = {
        handle: `${user_selected_handle}`,
        profilePictureUri: null,
        followModule: null,
      };

//this part will model the authentication query
      const createProfile_const = await client.mutate({
        //x-access-token header puts in the request with your authentication token.
        context: {
          headers: {
            "x-access-token": token,
          },
        },

        mutation: /*what mutation do we need to create a profile?*/,
        variables: {
          /* what variable(s) to we need?*/,
        },
      });
      console.log("attempting to createprofile for: ", user_selected_handle);
      if ((/* what does this query return on success? */) != undefined) {
        console.log(
          "create profile successful:",
          `${request.handle}.test`,
          "created at txHash:",
          /* what does this query return on success? */,
        );
        setHandle(`${request.handle}.test`);
        return createProfile_const;
      } else {
        console.log("create profile failed, try again!:", createProfile_const.data?.createProfile?.reason);
        setOpen(true);
      }
    } catch (err) {
      console.log("Error creating profile: ", err);
      setOpen(true);
    }
  }
}

6. 😎 Make a post!!

This next part will involve some information that we have provided for you in packages/react-app/src/constants.js

It'll involve LENS_HUB, the address of the lens contract we want to read/write from (note this is a proxy contract, we highly recommend that you check these out as they can be very useful)

It will involve the ABI, Application Binary Interface, which gives us the information needed to talk to the smart contract, like what functions/parameters and what they return

Let's find the function post in the abi in constants.js

        {
          lock: true,
          address: "{{LENS_HUB}}",
          name: "post",
          abi: {
            inputs: [
              {
                components: [
                  { internalType: "uint256", name: "profileId", type: "uint256" },
                  { internalType: "string", name: "contentURI", type: "string" },
                  { internalType: "address", name: "collectModule", type: "address" },
                  { internalType: "bytes", name: "collectModuleInitData", type: "bytes" },
                  { internalType: "address", name: "referenceModule", type: "address" },
                  { internalType: "bytes", name: "referenceModuleInitData", type: "bytes" },
                ],
                internalType: "struct DataTypes.PostData",
                name: "vars",
                type: "tuple",
              },
            ],
            name: "post",
            outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
            stateMutability: "nonpayable",
            type: "function",
          },

          params: [""],
        },

This is how you instantiate a contract object using ethers.js - note this is extremely redundant and there is a simpler way to parse the abi information, we challenge you to do this, but for a learning example this works! Hint: the contract we want is [1] and the function we want is [30]

  const postContract = new ethers.Contract(LENS_HUB, [LENS_ABI?.children[1].children[30].abi], localProvider);
  const postContractWSigner = postContract.connect(userSigner);

Here are the parameters we will need to pass to the post function (from Lens Developer Docs)

    const Id = await lens_id.getProfileIdByHandle(`${handle}`);
    console.log(Id);
    //convert ID to hex
    const profileId = `${ethers.utils.hexlify(Id)}`;
    const contentURI = "https://ipfs.io/ipfs/Qmby8QocUU2sPZL46rZeMctAuF5nrCc7eR1PPkooCztWPz";
    const collectModule = "0x0BE6bD7092ee83D44a6eC1D949626FeE48caB30c";
    const collectModuleInitData = "0x0000000000000000000000000000000000000000000000000000000000000001";
    const referenceModule = "0x0000000000000000000000000000000000000000";
    const referenceModuleInitData = "0x00";

Lets pass this info to the post function - and see what we get, we have gone ahead and made a tab called "Your Lens Profile" if you have gotten this far try clicking the button, it should show your handle as well as your first post gm 🌿 gm 🌿

πŸŽ‰ Congratulations! You've built your first Lens application!

6. πŸš€ Lets bundle up our app and Ship it!

First, we need to optimize our build before we deploy it to

yarn build

Use a file storage like IPFS or Surge to ship your app!

yarn surge

πŸ“š Additional Resources

Documentation, tutorials, challenges, and many more resources, visit: docs.scaffoldeth.io

πŸ”­ Learning Solidity

πŸ“• Read the docs: https://docs.soliditylang.org

πŸ“š Go through each topic from solidity by example editing YourContract.sol in πŸ— scaffold-eth

πŸ“§ Learn the Solidity globals and units

πŸ’¬ Support Chat

Join the telegram support chat πŸ’¬ to ask questions and find others building with πŸ— scaffold-eth!


πŸ™ Please check out our Gitcoin grant too!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Generated from scaffold-eth/scaffold-eth