Farcaster L1 API Write Endpoints

See the Free Hubs section of our home page for a list of free read/write hubs to use for Farcaster L1 writes!

Farcaster L2 API Write Endpoints

Cast is built as the first Farcaster Layer 2, functioning as an open-source, federated hub. To
authenticate with our Farcaster L2 APIs, you need to both create a GraphQL auth
token and a signer, following these steps:

The endpoint to complete writes is as follows, please use https://protocol.wield.co/graphql as your GraphQL endpoint:

Step 1: Retrieve Sign-in Message

Obtain a sign-in message using your external account (such as one managed by Metamask) and not a signer account. Use the GET_ACCOUNT_SIGNIN_MESSAGE query with your custody address and chainId set to 1.

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

export const GET_ACCOUNT_SIGNIN_MESSAGE = gql`
  query GET_ACCOUNT_SIGNIN_MESSAGE($address: String!, $chainId: Int!) {
    AccountQuery {
      getAccountSigninMessage(address: $address, chainId: $chainId)
    }
  }
`;
const { data, refetch } = useQuery(GET_ACCOUNT_SIGNIN_MESSAGE, {
  skip: !currentAddress,
  variables: {
    address: currentAddress,
    chainId: 1,
  },
});

Step 2: Sign the Message

Use the eth_personalsign method to sign the message you retrieved in the previous step.

import { useSignMessage } from "wagmi";
const { signMessage, isError, isLoading } = useSignMessage({
  message: signature,
  onSuccess: (token) => {
    callback?.({ token, address: currentAddress, type: "SIGNATURE" });
  },
});

Step 3: Exchange the Signature for an AccessToken

After signing the message, use the AUTH_BY_SIGNATURE mutation to exchange the signature for an accessToken. This token will be used for subsequent authenticated API requests.

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

const CORE_ACCOUNT_FIELDS = gql`
  fragment CoreAccountFields on Account {
    _id
    username
    profileImage {
      _id
      src
      isVerified
      verificationExternalUrl
    }
    bio {
      raw
      json
    }
    address {
      _id
      address
      chain {
        chainId
        name
      }
    }
  }
`;

export const AUTH_BY_SIGNATURE = gql`
  ${CORE_ACCOUNT_FIELDS}
  mutation AUTH_BY_SIGNATURE(
    $address: String!
    $chainId: Int!
    $signature: String!
  ) {
    authBySignature(
      address: $address
      chainId: $chainId
      signature: $signature
    ) {
      code
      success
      message
      accessToken
    }
  }
`;
const [
  _onSignin,
  { loading: signinLoading, data: signinData, error: signinError },
] = useMutation(AUTH_BY_SIGNATURE);
Cookies.set(config.AUTH_KEY, data.authBySignature.accessToken, {
  domain: config.COOKIE_DOMAIN,
  expires: 180,
});

Step 4: Create a localStorage signer

import * as ed from "@noble/ed25519";
import { gql, useMutation } from "@apollo/client";

async function generateAndStoreKeyPair(key) {
  try {
    const privateKey = ed.utils.randomPrivateKey();
    const publicKey = await ed.getPublicKeyAsync(privateKey);

    // Prepare the object to store
    const keyPair = {
      publicKey: Buffer.from(publicKey).toString("hex"),
      privateKey: Buffer.from(privateKey).toString("hex"),
    };

    // Store the key pair in local storage
    localStorage.setItem(key, JSON.stringify(keyPair));

    return keyPair;
  } catch (error) {
    console.error("Error generating or storing key pair:", error);
  }
}

export const ADD_RECOVERER = gql`
  ${CORE_ACCOUNT_FIELDS}
  mutation ADD_RECOVERER(
    $address: String
    $signature: String
    $type: String
    $id: String
  ) {
    addRecoverer(
      address: $address
      signature: $signature
      type: $type
      id: $id
    ) {
      code
      success
      message
      account {
        ...CoreAccountFields
      }
    }
  }
`;
const [_onAddRecoverer, { loading: addRecovererLoading }] =
  useMutation(ADD_RECOVERER);

Step 5: Send messages

// Helper function to make requests
export const makeRequest = async (
  messageType,
  body,
  fid,
  overrides = {},
  bodyOverrides = {}
) => {
  const message = await makeMessage({
    messageType,
    body,
    fid,
    overrides,
  });
  let isExternal = fid?.slice(0, 2) === "0x" ? true : false;
  if (!isExternal) {
    // it can also be external if any of the keys or subkeys of bodyOverrides contains 0x
    isExternal = Object.keys(bodyOverrides).some((key) => {
      if (typeof bodyOverrides[key] === "object") {
        return Object.keys(bodyOverrides[key]).some((subkey) => {
          return bodyOverrides[key][subkey]?.slice(0, 2) === "0x";
        });
      }
      return bodyOverrides[key]?.slice?.(0, 2) === "0x";
    });
  }
  const token = Cookies.get(config.AUTH_KEY);
  const response = await fetchWithoutAuth(
    "https://protocol.wield.co/farcaster/v2/message",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        authorization: token ? `Bearer ${token}` : "",
      },
      body: JSON.stringify({
        isExternal,
        message,
        bodyOverrides,
      }),
    }
  );
Language
Click Try It! to start a request and see the response here!