Farcaster Auth
The easiest way to manage signers for your Farcaster applications
One of the most difficult things to do in the Farcaster ecosystem is allowing users to make writes to the network from your app. Thankfully Pinata makes this easy with Farcaster Auth.
Primer to Farcaster Signers
In the Farcaster ecosystem there are a few ways you can make writes to the network, and they all revolve around the ED25519 signer. All Farcaster accounts are sets of public and private keypairs, or simply EOA wallets. Instead of prompting users to paste in their mnemonic seed phrase into an app, the more popular approach is to create a new keypair that the user can approve to be used to write on their behalf. This is much safer as the user can revoke these keypairs at any time.
The only problem is managing the signer key. Some developers store the key in a user’s local storage, which could get deleted if they clear their browser history or do some kind of cache clear. Others might manage the signers themselves in their database, but there are liability risks here. Farcaster Auth takes both of those problems away with an API and SDK that can mange the signers and create a simpler flow for users.
How it works
A high level view of Farcaster Auth looks something like this
- Create a new signer with
POST /signers
- Sign the public key returned by the API with your
FARCASTER_DEVELOPER_MNEMONIC
- Send a request with the signature payload to register the signer with warpcast
- API returns a polling
token
and adeep_link_url
. Use thetoken
to check the status of the approval, then give the end user thedeep_link_url
in the form of a QR code or link that will open Warpcast - Continue to poll the token, and once the signer is approved the process will be complete. The
signer_id
at this point can be used to make post requests.
Using Signers
Once you have an approved signer you can fetch the signerId
from GET /signers?fid=USER_FID
and filter by FID if you need to. Once you have the signerId
you can use it in other endpoints like sending casts.
Getting Started
To get started with Farcaster Auth you will need to get a Pinata API Key on a paid Pinata account, which you can view pricing here.
You will also need an App FID. Generally you would make a fresh Farcaster account for your app, like @photocast which as an FID of 327481
.
You will also need the mnemoic seed phrase for your app account, as this will sign the key and let the user know what app they are signing into when approving it in Warpcast. This is usually provided to you when creating the account for backup purposes. If you had an env file it might look something kike this:
FARCASTER_DEVELOPER_MNEMONIC=pinata confetti ipfs farcaster cloud developer send it
Signer Flow
Setup ENV variables
As stated previously you will need to make sure you have your App FID and App mnemomic phrase somewhere secure where you can access them in the following steps. Doing in Javascript might look something like this.
Create Signer with `POST /signers`
After declaring some types and importing viem/accounts
we can declare the constants and start the process of creating a signer.
import { mnemonicToAccount } from "viem/accounts";
const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
name: "Farcaster SignedKeyRequestValidator",
version: "1",
chainId: 10,
verifyingContract: "0x00000000fc700472606ed4fa22623acf62c60553",
} as const;
const SIGNED_KEY_REQUEST_TYPE = [
{ name: "requestFid", type: "uint256" },
{ name: "key", type: "bytes" },
{ name: "deadline", type: "uint256" },
] as const;
const appFid = process.env.FARCASTER_DEVELOPER_FID;
const account = mnemonicToAccount(
process.env.FARCASTER_DEVELOPER_MNEMONIC
);
async function createSigner() {
const res = await fetch("https://api.pinata.cloud/v3/farcaster/signers", {
method: "POST",
headers: {
'Content-Type': "application/json",
"Authorization": `Bearer YOUR_PINATA_JWT`
},
body: JSON.stringify({
app_fid: parseInt(appFid, 10)
})
});
const signerInfo: any = await res.json();
console.log(signerInfo)
const { data }: { data: { signer_uuid: string, public_key: string, signer_approved: string } } = signerInfo;
}
Sign Key Provided by Farcaster Auth
Now that the key is created we will need to sign the key with our developer Farcaster account
/// previous code
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: `0x${data.public_key}`,
deadline: BigInt(deadline),
}
});
Register Signer with Warpcast
Once the key is signed we can now make a request to Warpcast to register the key.
Since this is non-sponsored managed signers the user will need to pay warps at least once. After that the key is linked to your Pinata account and remains active until the usere revokes it.
// previous code
const registerResponse = await fetch(`https://api.pinata.cloud/v3/farcaster/register_signer_with_warpcast`, {
method: "POST",
headers: {
'Content-Type': "application/json",
"Authorization": `Bearer YOUR_PINATA_JWT`
},
body: JSON.stringify({
signer_id: data.signer_uuid,
signature: signature,
deadline: deadline,
app_fid: requestFid,
app_address: account.address
})
})
const warpcastPayload = await registerResponse.json()
//"data": {
// "signer_id": "ee5605a0-9833-4d30-8c22-7ac315061ee7",
// "token": "0x2ef159dc1e9f6a2sb0d2353f",
// "deep_link_url": "farcaster://signed-key-request?token=0x2efb59dv1e9f642fb07f353f",
// "status": "pending_approval"
//}
Poll Signer for Approval Status
At this point we should immediately start polling the signer, waiting for the user to approve the key. You might have noticed in the response to /register_signer_with_warpcast
is the deep_link_url
. This is a special URL that when opened on a mobile device with Warpcast installed, will prompt the user to approve the signer/login. This can be given to the user in the form of a QR code or just a link that the user opens.
Once they approve the signer we’ll get a response back from Warpcast that will register the key with that user’s account and make it accessible via Farcaster Auth. To do that we’ll use poll_warpcast_signer
with a query parameter of token
that’s provided by the previous request to register_signer_with_warpcast
.
// Previous code...
const pollSigner = await fetch(`https://api.pinata.cloud/v3/farcaster/poll_warpcast_signer?token=${warpcastPayload.data.token}`, {
method: 'POST',
headers: {
'Content-Type': "application/json",
"Authorization": `Bearer YOUR_PINATA_JWT`
}
})
const pollSignerRes = await pollSigner.json()
return pollSignerRes.data.result
//"signedKeyRequest": {
// "token": "0x2ef159dc1e9f6a2sb0d2353f",
// "deeplinkUrl": "farcaster://signed-key-request?token=0x2efb59dv1e9f642fb07f353f",
// "key": "0xe57c09c48b0ba27d95676309416134ad1409d3cg27ccb354b73eb819aa117f2f",
// "requestFid": 6023,
// "state": "completed",
// "isSponsored": false,
// "userFid": 6023
//}
If successful you should get a response with a completed
state, at which point you can now use the signer_id
returned in the Register Signer step.
Get Signers
After registering you can get the signer_id
at any point using GET /signers
endpoint with a query parameter of fid
if you need to filter the results.
async function getSigners(fid?: number){
try {
const req = await fetch(`https://api.pinata.cloud/v3/farcaster/signers?fid=${fid}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer YOUR_PINATA_JWT`,
}
});
const res = await req.json();
const resultData = res.data
return resultData
} catch (error) {
throw error;
}
};
//"signers": [
// {
// "id": 25,
// "signer_uuid": "bd9cea0c-ab31-4c71-a46f-a169ba626192",
// "fid": 6023,
// "public_key": "e57c09c48b0ba27d95676309416134ad1409d3cg27ccb354b73eb819aa117f2f",
// "signer_approved": true,
// "revoked": false
// }
//],
//"next_page_token": "eyJvZmZzZXQiOiIyNSJ9"
With the signer_id
you can then use it in other API calls like POST /casts
to send a cast.
curl --location 'https://api.pinata.cloud/v3/farcaster/casts' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_PINATA_JWT' \
--data '{
"castAddBody": {
"text": "Hello World!",
"parentUrl": "https://warpcast.com/~/channel/pinata"
},
"signerId": "SIGNER_ID"
}'