Back to blog
How to Send a Cast with the Pinata Farcaster API
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/pinata
as 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!