Back to blog

How to Send a Cast with the Pinata Farcaster API

How to Send a Cast with the Pinata Farcaster API

Steve

If you’re building on Farcaster, chances are that you’ve been able to fetch casts and other useful information with something like the Pinata Farcaster API without too much trouble, but the thought of sending casts is a bit daunting. There’s not too much documentation out there on the process or guides that help simplify it, so that’s what we’re going to do today! Hopefully, it’s enough to get you started so you can start building a client today.

Setup

The most crucial pieces you’ll need for sending a cast via API is the FID for the user, as well as a signer key. The flow of getting a signer key is a bit complicated, and we would highly recommend reading this guide before going any further. Once you do have an FID and a signer key, keep them somewhere safe, as we’ll use them in just a bit.

To write this code, we’ll be using bun.sh so if you haven’t installed that feel free to, otherwise you can use Node.js or TypeScript. To get our project set up, let’s run the following commands in the terminal.

mkdir cast-api && cd cast-api && bun init

This will prompt you for the name of the package, which you could also call cast-api, and then you can just use the default index.ts filename. Then you will want to install some dependencies.

bun install @noble/ed25519 @farcaster/core

Once that is done, let’s make a new .env file.

touch .env

Open up that .env file with your text editor and paste into the following environment variables.

PRIVATE_KEY=
FID=

The PRIVATE_KEY would be the signer key we got earlier from following the How to Sign In with Warpcast tutorial, and the FID would be the Farcaster ID for the user we’re casting for. Remember that those two go hand in hand, the signer key is for that particular FID. Now we can open the project with our text editor and start writing the code!

Writing the Code

With your text editor, let’s open up the index.ts file and add the following code.

import {
  Message,
  NobleEd25519Signer,
  FarcasterNetwork,
  makeCastAdd,
} from "@farcaster/core";
import { hexToBytes } from "@noble/hashes/utils";

const FID = process.env.FID ? parseInt(process.env.FID) : 0;
const SIGNER = process.env.PRIVATE_KEY || "";

index.ts

Here we’ll just import our dependencies from the Farcaster Core and Noble libraries. Right below it we’ll declare our FID and SIGNER constants, which we’ll used in the API call. Next let’s write the beginning of our function and add some set-up options.

import {
  Message,
  NobleEd25519Signer,
  FarcasterNetwork,
  makeCastAdd,
} from "@farcaster/core";
import { hexToBytes } from "@noble/hashes/utils";

const FID = process.env.FID ? parseInt(process.env.FID) : 0;
const SIGNER = process.env.PRIVATE_KEY || "";

async function sendCast(message: string, parentUrl: string) {
  try {
    // Add data options
    const dataOptions = {
      fid: FID,
      network: FarcasterNetwork.MAINNET,
    };
		// Prepare signer key
    const privateKeyBytes = hexToBytes(SIGNER.slice(2));
    const ed25519Signer = new NobleEd25519Signer(privateKeyBytes);

  } catch (error) {
    console.log("problem sending cast:", error);
  }
}

index.ts

After we declare our function with the message and parentUrl params, we’ll also declare our dataOptions, which includes the FID for the user sending the cast and the mainnet network we’ll be casting to. We’ll also prep our ed25519Signer  by changing the PRIVATE_KEY to bytes and passing it into NobleEd25519Signer. Now let’s prepare our cast to be sent.

import {
  Message,
  NobleEd25519Signer,
  FarcasterNetwork,
  makeCastAdd,
} from "@farcaster/core";
import { hexToBytes } from "@noble/hashes/utils";

const FID = process.env.FID ? parseInt(process.env.FID) : 0;
const SIGNER = process.env.PRIVATE_KEY || "";

async function sendCast(message: string, parentUrl: string) {
  try {

    const dataOptions = {
      fid: FID,
      network: FarcasterNetwork.MAINNET,
    };

    const privateKeyBytes = hexToBytes(SIGNER.slice(2));

    const ed25519Signer = new NobleEd25519Signer(privateKeyBytes);
		// Create castBody
    const castBody = {
      text: message,
      embeds: [],
      embedsDeprecated: [],
      mentions: [],
      mentionsPositions: [],
      parentUrl: parentUrl,
    };
    
		// Prepare castBody
    const castAddReq = await makeCastAdd(castBody, dataOptions, ed25519Signer);
    const castAdd: any = castAddReq._unsafeUnwrap();
    const messageBytes = Buffer.from(Message.encode(castAdd).finish());
   
  } catch (error) {
    console.log("problem sending cast:", error);
  }
}

index.ts

The Farcaster API makes it pretty easy to configure the contents of your cast so that they’ll be recognized by other clients, and you can reference the options here for more info. In our cast, we’ll make it pretty simple by just including the main message and a parentUrl which will cast it to a specific channel. After the object is put together, we’ll prepare the cast request with makeCastAdd which takes in our castBody, the dataOptions, and our ed25519Signer. After that, we’ll make a crucial step to _unsafeUnwrap() the request and then encode it to messageBytes. Now we can finally send the cast and call the function.

import {
  Message,
  NobleEd25519Signer,
  FarcasterNetwork,
  makeCastAdd,
} from "@farcaster/core";
import { hexToBytes } from "@noble/hashes/utils";

const FID = process.env.FID ? parseInt(process.env.FID) : 0;
const SIGNER = process.env.PRIVATE_KEY || "";

async function sendCast(message: string, parentUrl: string) {
  try {

    const dataOptions = {
      fid: FID,
      network: FarcasterNetwork.MAINNET,
    };

    const privateKeyBytes = hexToBytes(SIGNER.slice(2));

    const ed25519Signer = new NobleEd25519Signer(privateKeyBytes);

    const castBody = {
      text: message,
      embeds: [],
      embedsDeprecated: [],
      mentions: [],
      mentionsPositions: [],
      parentUrl: parentUrl,
    };

    const castAddReq = await makeCastAdd(castBody, dataOptions, ed25519Signer);
    const castAdd: any = castAddReq._unsafeUnwrap();
    const messageBytes = Buffer.from(Message.encode(castAdd).finish());
    
		// Make API request 
    const castRequest = await fetch(
      "https://hub.pinata.cloud/v1/submitMessage",
      {
        method: "POST",
        headers: { "Content-Type": "application/octet-stream" },
        body: messageBytes,
      },
    );
		// Parse API request results
    const castResult = await castRequest.json();
    console.log(castResult);
    return castResult
  } catch (error) {
    console.log("problem sending cast:", error);
  }
}
// Call our function
sendCast("Hello World from Bun.sh", "https://warpcast.com/~/channel/pinata");

index.ts

With our API request, we send it over to https://hub.pinata.cloud/v1/submitMessage with our messageBytes as the body, then parse and return the results. At the very end of the file, we’ll call our function with Hello World from Bun.sh as the message, and https://warpcast.com/~/channel/pinataas our channel. Now, go back to the terminal and run it!

bun run index.ts

If it works, you should get a response like this:

{
  data: {
    type: "MESSAGE_TYPE_CAST_ADD",
    fid: 6023,
    timestamp: 101313121,
    network: "FARCASTER_NETWORK_MAINNET",
    castAddBody: {
      embedsDeprecated: [],
      mentions: [],
      parentUrl: "https://warpcast.com/~/channel/pinata",
      text: "hello world from Bun.sh",
      mentionsPositions: [],
      embeds: []
    }
  },
  hash: "0xb513a94af0427e0cf589fdcb87854c3b64d5023f",
  hashScheme: "HASH_SCHEME_BLAKE3",
  signature: "gzFIY8POjBIvwDwIWFKf...v/efYrAA==",
  signatureScheme: "SIGNATURE_SCHEME_ED25519",
  signer: "0xbe...222b"
}

That’s it! Just like that, you’ve sent a cast via API with Bun 🎉  If you need a reference at any time, you can check out the source code here.

Wrapping up

While there’s not too much code used to send a cast, the overall flow is something Pinata sees as a possibility to improve, and it’s something we’re looking into. We want to empower developers to build on Farcaster and IPFS, and sometimes that includes covering basics like this on a lower level before introducing an abstraction.

Want to stay tuned for more updates on what Pinata may release? Be sure to join /pinata on Warpcast and check out our Farcaster API to start building today! Happy Pinning!

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.