Neynar Documentation

Documentation Index

Fetch the complete documentation index at: /llms.txt

Use this file to discover all available pages before exploring further.

Create your Farcaster account programmatically and publish your first message.The example shows you how to:

This example can be checked out as a fully functional repository here.

Requirements

See running a node for more information on how to set up a node.

Custody address vs signer

In order to register an account and send messages, you need 2 pairs of keys:

1. Set up constants

import {
  ID_GATEWAY_ADDRESS,
  idGatewayABI,
  KEY_GATEWAY_ADDRESS,
  keyGatewayABI,
  ID_REGISTRY_ADDRESS,
  idRegistryABI,
  FarcasterNetwork,
} from '@farcaster/hub-web';
import { zeroAddress } from 'viem';
import { optimism } from 'viem/chains';
import { generatePrivateKey, privateKeyToAccount, toAccount } from "viem/accounts";

const CUSTODY_PRIVATE_KEY = '<REQUIRED>'; // A private key corresponding with any ETH address.
const OP_PROVIDER_URL = '<REQUIRED>'; // Alchemy or Infura url
const RECOVERY_ADDRESS = zeroAddress; // Optional, using the default value means the account will not be recoverable later if the mnemonic is lost
const SIGNER_PRIVATE_KEY: Hex = zeroAddress; // Optional, using the default means a new signer will be created each time

const HUB_URL = 'crackle.farcaster.xyz:3383'; // URL + Port of the node
const USE_SSL = false; // set to true if talking to a node that uses SSL (3rd party hosted nodes or nodes that require auth)
const FC_NETWORK = FarcasterNetwork.MAINNET; // Network of the node

const CHAIN = optimism;

const IdGateway = {
  abi: idGatewayABI,
  address: ID_GATEWAY_ADDRESS,
  chain: CHAIN,
};
const IdContract = {
  abi: idRegistryABI,
  address: ID_REGISTRY_ADDRESS,
  chain: CHAIN,
};
const KeyContract = {
  abi: keyGatewayABI,
  address: KEY_GATEWAY_ADDRESS,
  chain: CHAIN,
};

2. Register and pay for storage

Create a function to register an FID and pay for storage:

const getOrRegisterFid = async (): Promise<number> => {
  const balance = await getBalance(walletClient, { address: account.address });
  const existingFid = (await readContract(walletClient, {
    ...IdContract,
    functionName: "idOf",
    args: [account.address],
  })) as bigint;

if (balance === 0n && existingFid === 0n) {
    throw new Error("No existing Fid and no funds to register an fid");
  }

if (existingFid > 0n) {
    return parseInt(existingFid.toString());
  }

const price = await readContract(walletClient, {
    ...IdGateway,
    functionName: "price",
  });

if (balance < price) {
    throw new Error(`Insufficient balance to rent storage, required: ${price}, balance: ${balance}`);
  }

const { request: registerRequest } = await simulateContract(walletClient, {
    ...IdGateway,
    functionName: "register",
    args: [RECOVERY_ADDRESS],
    value: price,
  });
  const registerTxHash = await writeContract(walletClient, registerRequest);
  const registerTxReceipt = await waitForTransactionReceipt(walletClient, { hash: registerTxHash });

if (registerTxReceipt.logs[0]) {
    const registerLog = decodeEventLog({
      abi: idRegistryABI,
      data: registerTxReceipt.logs[0].data,
      topics: registerTxReceipt.logs[0].topics,
    });

const fid = parseInt(registerLog.args["id"]);
    return fid;
  } else {
    throw new Error("Did not receive logs for registered fid");
  }
};

const fid = await getOrRegisterFid();

3. Add a signer

Now, we will add a signer to the key registry:

const getOrRegisterSigner = async (fid: number) => {
  if (SIGNER_PRIVATE_KEY !== zeroAddress) {
    const privateKeyBytes = fromHex(SIGNER_PRIVATE_KEY, "bytes");
    return privateKeyBytes;
  }

const privateKey = ed25519.utils.randomPrivateKey();
  const publicKey = toHex(ed25519.getPublicKey(privateKey));

const localAccount = toAccount(account);
  const eip712signer = new ViemLocalEip712Signer(localAccount);
  const metadata = await eip712signer.getSignedKeyRequestMetadata({
    requestFid: BigInt(fid),
    key: fromHex(publicKey, "bytes"),
    deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60),
  });

const metadataHex = toHex(metadata.unwrapOr(new Uint8Array()));

const { request: signerAddRequest } = await simulateContract(walletClient, {
    ...KeyContract,
    functionName: "add",
    args: [1, publicKey, 1, metadataHex],
  });

const signerAddTxHash = await writeContract(walletClient, signerAddRequest);
  await waitForTransactionReceipt(walletClient, { hash: signerAddTxHash });
  await new Promise((resolve) => setTimeout(resolve, 30000));
  return privateKey;
};

const signer = await getOrRegisterSigner(fid);

4. Register an fname

const registerFname = async (fid: number) => {
  const fname = `fid-${fid}`;
  const timestamp = Math.floor(Date.now() / 1000);
  const localAccount = toAccount(account);
  const signer = new ViemLocalEip712Signer(localAccount);
  const userNameProofSignature = await signer.signUserNameProofClaim(
    makeUserNameProofClaim({ name: fname, timestamp: timestamp, owner: account.address }),
  );

const response = await axios.post("https://fnames.farcaster.xyz/transfers", {
    name: fname,
    from: 0,
    to: fid,
    fid: fid,
    owner: account.address,
    timestamp: timestamp,
    signature: bytesToHex(userNameProofSignature._unsafeUnwrap()),
  });
  return fname;
};

const fname = await registerFname(fid);

5. Write to Snapchain

const submitMessage = async (resultPromise: HubAsyncResult<Message>) => {
  const result = await resultPromise;
  const messageSubmitResult = await hubClient.submitMessage(result.value, metadata);
};

const signer = new NobleEd25519Signer(signerPrivateKey);
const dataOptions = { fid: fid, network: FC_NETWORK };
const userDataPfpBody = { type: UserDataType.USERNAME, value: fname };
await submitMessage(makeUserDataAdd(userDataPfpBody, dataOptions, signer));
await submitMessage(
  makeCastAdd({ text: "Hello World!" }, dataOptions, signer)
);

Now, you can view your profile on any farcaster client. To see it on Warpcast, visit https://warpcast.com/@<fname>.