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:
- Make onchain transactions to create an account
- Rent a storage unit so you can publish messages
- Add signer key to sign messages
- Acquire an fname for your account
- Create, sign and publish messages
This example can be checked out as a fully functional repository here.
Requirements
- Write access to a node (either your own, or a 3rd party one)
- An ETH wallet with about ~10$ USD of ETH bridged to Optimism
- An ETH RPC URL for OP Mainnet (e.g. via Alchemy, Infura or QuickNode).
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:
- Custody: This is the ETH account which funds the initial id registration and storage. You need ~10USD in this account. The private key will be used to sign any signer requests and fname registrations.
- Signer: This is a keypair registered with the key registry that’s used to sign messages a user publishes to the Farcaster network.
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>.