fname.js

raw

#!/usr/bin/env node
/*
 * Claim a Farcaster username (fname). The fname registry is off-chain and free —
 * it just wants an EIP-712 signature from the custody wallet that owns the fid.
 *   node fname.js agentatwork
 */
const { Wallet } = require("ethers");
const fs = require("fs");

const NAME = process.argv[2];
if (!NAME) throw new Error("usage: node fname.js <name>");

// canonical fname registry domain
const DOMAIN = {
  name: "Farcaster name verification",
  version: "1",
  chainId: 1,
  verifyingContract: "0xe3be01d99baa8db9905b33a3ca391238234b79d1",
};
const TYPES = {
  UserNameProof: [
    { name: "name", type: "string" },
    { name: "timestamp", type: "uint256" },
    { name: "owner", type: "address" },
  ],
};

(async () => {
  const keys = JSON.parse(fs.readFileSync(__dirname + "/keys.json", "utf8"));
  const st = JSON.parse(fs.readFileSync(__dirname + "/farcaster.json", "utf8"));
  const w = new Wallet(keys.privateKey);
  const fid = Number(st.fid);
  const timestamp = Math.floor(Date.now() / 1000);

  const signature = await w.signTypedData(DOMAIN, TYPES,
    { name: NAME, timestamp, owner: w.address });

  const body = { name: NAME, from: 0, to: fid, fid, owner: w.address, timestamp, signature };
  const r = await fetch("https://fnames.farcaster.xyz/transfers", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });
  const text = await r.text();
  if (!r.ok) throw new Error(`${r.status}: ${text}`);
  console.log("registered:", NAME, "->", text.slice(0, 300));

  fs.writeFileSync(__dirname + "/farcaster.json",
    JSON.stringify({ ...st, fname: NAME }, null, 2));
  fs.chmodSync(__dirname + "/farcaster.json", 0o600);
})().catch(e => { console.error("ERROR:", e.message); process.exit(1); });