Back to blog

How to Sign In With Warpcast

How to Sign In With Warpcast

Justin Hunter

If you’re building an application on Farcaster that needs to write to Hubs, the decentralized data storage network created by the protocol, then you’ll have to have an auth mechanism that creates what is called a “signer,” and there are two ways to do this. You can create a signers by making direct smart contract calls on the Optimism network, or you can use Warpcast like an OAuth client. This guide will show you how to use Warpcast like an OAuth client.

For this tutorial, we’re going to be creating an Express server using Bun and Typescript. Let’s get started.

Creating the Server

Change into the directory where you keep your development projects, and let’s create a new project for our server. We’ll call it swooop-server:

mkdir swooop-server && cd swooop-server

We’re going to use Bun to make our lives easier and to give us Typescript out of the box without fighting with compiler settings. If you don’t have Bun installed on your machine, follow this guide.

If and when Bun is installed, simply run the following command from your project’s folder:

bun init

This will act similarly to npm init, but it will give you the option to create a Typescript project and generate your index.ts file. This is a very simple Bun project, but it’s not an Express server yet. Let’s install Express. Run the following command:

bun add express

Now, in your index.ts file, replace everything with:

import express, { Express,  } from "express";

const app: Express = express();
const port = process.env.PORT || 3000;

app.use(express.json());

app.listen(port, () => {
  console.log(`[server]: Server is running at http://localhost:${port}`);
});

We now have a basic Express server with no routes.

Sign In With Warpcast

In order to create signers (the thing that allows users to send casts to the network), we need an FID (Farcaster ID) and an associated wallet mnemonic from an account that is already registered on Farcaster. Think of this like OAuth. You’ve surely signed into apps before using Google. Those apps had to register their app with Google. In the case of Farcaster, the app developer must register their account with the network.

Technically, you can use your personal FID and Farcaster mnemonic. If you want to do that, simply skip the next section and pay attention to the section explaining how to get your FID and mnemonic from Warpcast.

You can use the Warpcast mobile app to create a new account. When you do this, you’ll need to provide a name, bio, and a profile picture. There is an onchain fee associated with creating a new account, but Warpcast makes that easy to pay. Once you’ve done this, you will have an account dedicated to your new SwiftUI Farcaster client. Now, we need to get the FID and the wallet mnemonic for this account.

To find your FID, open Warpcast and go to your profile. Click the three dots in the top-right and then click About.

__wf_reserved_inherit

Once you have that, paste that into your .env file as the value for the FARCASTER_DEVELOPER_FID.

Now, to get the mnemonic that is associated with the wallet that was created for you when you created this Farcaster account, you can click on your avatar image in the top-left on Warpcast.

__wf_reserved_inherit

Click the gear icon, then on the next page, click Advanced. You should see a page that looks like this:

__wf_reserved_inherit

Click on “Reveal recovery phrase.” Copy that phrase and paste it in as the value for FARCASTER_DEVELOPER_MNEMONIC. Now, we’re ready to actually write code for our auth flow.

Quickly, before writing code, this is how auth works with signing in with Warpcast:

  1. The app must create a ed25519 keypair
  2. The app must create a message and sign it with the private key generated in step one
  3. The app must make request is made to a Warpcast API using the signature generated in the last step and the public key generated in step 1. This will generate a deeplink URL and a token used for checking status
  4. The app must return to the client the deeplink URL (and depending on your use case the private and public keys)
  5. The user using the client app must click the deeplink URL or scan a QR code which takes them to Warpcast
  6. The user must approve the registration of this “signer” (remember the Google OAuth flow I mentioned before? Same concept) by approving an onchain transaction that adds the public key used in step 3 to a smart contract registry
  7. The server/app must poll another Warpcast API using the polling token generated in step 3 to get the status of the end user’s approval
  8. When the approval is complete, the server must send necessary data to the client to inform the client that auth is complete

That’s a lot, right? It is. But we’re going to do it not because it’s easy but because we thought it would be easy!

Now, we code.

In your server code, open the index.ts file. Let’s add our first route like this:

app.post("/sign-in", async (req: express.Request, res: express.Response) => {
  try {
    const signInData = await signInWithWarpcast();
    if(!signInData) {
      res.status(500).json({error: "Failed to sign in user"});
    }
    if(signInData) {
      res.json({
        deepLinkUrl: signInData?.signerApprovalUrl, 
        pollingToken: signInData?.token,
        publicKey: signInData?.publicKey,
        privateKey: signInData?.privateKey,
      });
    }
    else{
      res.status(500).json({error: "Failed to get farcaster user"});
    }
  } catch (error) {
    res.status(500).json({error: error});
  }
});

Let’s take a look at the file so far. We have our first route defined. You can see it’s a POST endpoint called sign-in. Inside that route, we’re calling the necessary functions to kick off that 8-step process from above. While we haven’t written the signInWithWarpcast function, we can see the results give us a deepLinkUrl, a pollingToken, a publicKey, and a privateKey. All of these are returned to the native app. Before we write that function, we need to install some dependencies. Run the following:

bun add @noble/ed25519 viem

Now, let’s write our signInWithWarpcast function. At the top of your file, below the other imports, add the following:

import * as ed from "@noble/ed25519";
import { mnemonicToAccount } from "viem/accounts";

Now, for the function itself, you may want to do this in another file, but for simplicity, we’re going to keep it in this same file. Add the following function above your route and below the imports and config:

export const signInWithWarpcast = async () => {
  const privateKeyBytes = ed.utils.randomPrivateKey();
  const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
  
  const keypairString = {
    publicKey: "0x" + Buffer.from(publicKeyBytes).toString("hex"),
    privateKey: "0x" + Buffer.from(privateKeyBytes).toString("hex"),
  };
  const appFid = process.env.FARCASTER_DEVELOPER_FID!;
  const account = mnemonicToAccount(
    process.env.FARCASTER_DEVELOPER_MNEMONIC!
  );

  const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
  const requestFid = parseInt(appFid);
  const signature = await account.signTypedData({
    domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
    types: {
      SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
    },
    primaryType: "SignedKeyRequest",
    message: {
      requestFid: BigInt(appFid),
      key: keypairString.publicKey as `0x`,
      deadline: BigInt(deadline),
    },
  });
  const authData = {
    signature: signature,
    requestFid: requestFid,
    deadline: deadline,
    requestSigner: account.address,

  }
  const {
    result: { signedKeyRequest },
  } = (await (
    await fetch(`https://api.warpcast.com/v2/signed-key-requests`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        key: keypairString.publicKey,
        signature,
        requestFid,
        deadline,
      }),
    })
  ).json()) as {
    result: { signedKeyRequest: { token: string; deeplinkUrl: string } };
  };
  const user: FarcasterUser = {
    ...authData,
    publicKey: keypairString.publicKey,
    deadline: deadline,
    token: signedKeyRequest.token,
    signerApprovalUrl: signedKeyRequest.deeplinkUrl,
    privateKey: keypairString.privateKey,
    status: "pending_approval",
  };
  return user;

};

There is a lot going on here, but you expected that, right? I mean, it is an 8-STEP PROCESS! Either way, let’s walk through this function. First, we’re using a library to generate our public and private keypair. However, we need the hex string version of those keys, so we convert both and create an object containing the string version of the public and private keys.

Next, we’re creating a EIP712 signed data object that will be signed with our private key which we just generated. We then create auth object that will used after our API call to Warpcast. That auth object contains our signature, the requestFid (which is our developer FID), the deadline (how long the message is available to use), and the signer (the private key created at the beginning).

Now, we make our API call to Warpcast with the request payload containing the signature, the publicKey we generated at the beginning, the requestFid, and the deadline. Once we get a response back, we merge that response with the auth object and return the entire object to the API route to be returned to the client.

The good news is that this was the most complicated function we’ll have to write. The bad news is that we’re not done with auth yet. Remember the client needs to poll to check the status of the end user approving the signer onchain? Let’s create that endpoint.

Above our sign-in endpoint, let’s add another endpoint like this:

app.get("/sign-in/poll", async (req: express.Request, res: express.Response) => {
  const {pollingToken} = req.query;
    try {
      const fcSignerRequestResponse = await fetch(
        `https://api.warpcast.com/v2/signed-key-request?token=${pollingToken}`,
        {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      const responseBody = (await fcSignerRequestResponse.json()) as {
        result: { signedKeyRequest: SignedKeyRequest };
      };
      console.log(responseBody)
      res.status(200).json({"state": responseBody.result.signedKeyRequest.state, "userFid": responseBody.result.signedKeyRequest.userFid});
    }
    catch (error) {
      res.status(500).json(error);
    }
  }
);

This is much simpler. The client will pass the polling token that was generated in the last API call into this request as a query string parameter and we hit another Warpcast API to see if the signer has been approved. When the signer is approved, the client application will need to respond to that and show the user as signed in and stop polling this endpoint.

With that, you have created an a server that will support signing in with Warpcast!

Conclusion

Farcaster is one of the most exciting developments in web3 lately. Developers are building increasingly complex and innovative applications atop the network. However, auth is still complicated. Hopefully, this article helps get you started.

And when your app is ready for media like images and video, Pinata has you covered with IPFS.

Subscribe to paid plan image

Share this post:

Stay up to date

Join our newsletter for the latest stories & product updates from the Pinata community.