Skip to content

fix: Correct Top8 fidget username handling and profile registration#1762

Open
Jhonattan2121 wants to merge 16 commits intocanaryfrom
fix/top8-dynamic-profile
Open

fix: Correct Top8 fidget username handling and profile registration#1762
Jhonattan2121 wants to merge 16 commits intocanaryfrom
fix/top8-dynamic-profile

Conversation

@Jhonattan2121
Copy link
Collaborator

@Jhonattan2121 Jhonattan2121 commented Feb 3, 2026

Overview

This PR fixes an issue where the Top8 fidget would default to "nounspacetom" for users who didn't have their username explicitly saved in the fidget configuration.

Changes

  • Fixed Top8 Fidget: Removed the hardcoded fallback to "nounspacetom" in Top8.tsx. If no username is provided, it now handles the empty state correctly instead of showing the default profile.
  • Improved Registration: Updated registerSpaceFid (in spaceStore.ts) and PublicSpace.tsx to ensure the user's actual username is passed and saved during the initial profile space creation.

Why

Previously, new profile spaces were being created without the specific username in the config, causing the fidgets to fall back to the default account. This ensures new users get their own Top8 data correctly configured upon registration.

Summary by CodeRabbit

  • New Features

    • Infer a Farcaster FID from a connected wallet and expose on-demand Farcaster signer creation; actions now request authorization only when needed.
    • Added backend infer-FID endpoint to support wallet→FID linking.
    • Optional setting to disable automatic settings backfill; improved Top8 username handling/backfill.
  • Bug Fixes

    • Show clear error toasts when space saves fail.
    • Top8 embed avoids rendering invalid iframe when username is blank.
    • Better onboarding modal behavior around authenticator readiness.
  • Style

    • Polished login modal and space creation/registration UX.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Replaces an authenticator-first Farcaster flow with wallet-address-based FID inference, on-demand signer/authenticator initialization, a new /api/fid-link/infer endpoint, readiness polling utilities, relaxed fidRegistrations DB typings, signer lifecycle APIs, UI/fidget updates, and related provider/store changes.

Changes

Cohort / File(s) Summary
Spaces UI
src/app/(spaces)/PublicSpace.tsx, src/app/(spaces)/s/[handle]/ProfileSpace.tsx
Removed Farcaster authenticator wiring in PublicSpace (useCurrentFid instead), simplified editability checks, adjusted registration/save flows, and added tabName prop to ProfileSpace.
Authenticator manager & modal
src/authenticators/AuthenticatorManager.tsx, src/common/components/templates/LoginModal.tsx, src/common/lib/authenticators/waitForAuthenticatorReady.ts
Guarded modal auto-open by setup step, switched initialization-queue updates to functional updates, made initializer rendering conditional, and added polling helper to await authenticator readiness.
Signer lifecycle & fidgets
src/fidgets/farcaster/index.tsx, src/fidgets/farcaster/components/CreateCast.tsx, src/fidgets/farcaster/components/CastRow.tsx, src/fidgets/ui/profile.tsx, src/fidgets/farcaster/Top8.tsx
Added on-demand signer APIs (hasSigner, ensureSigner, getOrCreateSigner), updated components to obtain signer at action time with loading guards/toasts, and changed Top8 username/iframe/backfill behavior.
Farcaster store & hook
src/common/data/stores/app/accounts/farcasterStore.ts, src/common/lib/hooks/useCurrentFid.ts
Added inferFidForCurrentIdentity(walletAddress) (signed request to new API) and made useCurrentFid prefer associated/inferred FIDs over authenticator-sourced FIDs.
Logged-in provider
src/common/providers/LoggedInStateProvider.tsx
Introduced wallet-based FID inference flow with fallback authenticator registration, used waitForAuthenticatorReady, and improved registerAccounts error handling and control flow.
API: fid-link & identities
src/pages/api/fid-link/infer.ts, src/pages/api/fid-link.ts, src/pages/api/space/identities.ts
Added POST /api/fid-link/infer with signature validation, Supabase checks, Neynar lookup and upsert logic; adjusted fid-link types and identities handlers (case-insensitive wallet matching, structured error responses).
Database schema & types
supabase/migrations/20240614000356_setup_db.sql, src/supabase/database.d.ts
Relaxed fidRegistrations columns to allow nulls for signature/signingPublicKey/signingKeyLastValidatedAt and added default for isSigningKeyValid; updated TS DB typings.
Required authenticators & dev signer
src/constants/requiredAuthenticators.ts, src/authenticators/farcaster/signers/NounspaceManagedSignerAuthenticator.tsx
Default required authenticators set to empty array; adjusted dev signer saved-data shape and ensured done() call after save.
Space store & config
src/common/data/stores/app/space/spaceStore.ts, src/config
Narrowed file-exists deletion logic, added 30s timeout to registry POSTs, and updated createInitialProfileSpaceConfigForFid to accept (fid, name).
Fidget backfill flag & types
src/common/fidgets/FidgetWrapper.tsx, src/common/fidgets/index.d.ts
Added disableSettingsBackfill flag usage and typing; conditionalized lastFetchSettings backfill behavior and adjusted Fidget type declaration formatting.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Client as Client App
  participant Store as App/Farcaster Store
  participant API as /api/fid-link/infer
  participant DB as Supabase
  participant Neynar as Neynar API

  User->>Client: Connect wallet / open app
  Client->>Store: inferFidForCurrentIdentity(walletAddress)
  Store->>API: POST {identityPublicKey,walletAddress,timestamp,signature}
  API->>DB: Verify identityPublicKey ↔ walletAddress
  alt NEYNAR_API_KEY present
    API->>Neynar: Lookup fid by wallet address
    Neynar-->>API: fid or null
  end
  alt fid found
    API->>DB: Upsert/insert fidRegistrations (preserve signer fields)
    API-->>Store: {fid,identityPublicKey,created,inferredFromAddress}
    Store-->>Client: use inferred fid
  else
    API-->>Store: null
    Store-->>Client: null (client may fall back to authenticator flow)
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Client as Client App
  participant AuthMgr as AuthenticatorManager
  participant Modal as LoginModal
  participant FarAuth as Farcaster Authenticator

  User->>Client: Trigger action needing signer
  Client->>AuthMgr: ensure Farcaster authenticator installed/initialized
  AuthMgr->>AuthMgr: install/init (functional queue + readiness)
  AuthMgr->>Modal: open initializer modal (if needed)
  Modal->>FarAuth: run initializer
  FarAuth-->>AuthMgr: mark initialized
  AuthMgr-->>Client: ready (waitForAuthenticatorReady true)
  Client->>FarAuth: getAccountFid / create signer
  FarAuth-->>Client: signer + fid
  Client-->>User: proceed with action
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

LGFTP

Suggested reviewers

  • j-paterson

Poem

A rabbit hops through code and keys,
I sniff a wallet, chase the breeze.
If Neynar whispers FIDs to me,
I tuck them safe beneath a tree.
Carrots, modals, signers — hop, hooray! 🥕🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary changes: fixing Top8 username handling (removing hardcoded fallback) and correcting profile registration logic (passing space name during FID-based registration).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/top8-dynamic-profile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
src/pages/api/space/identities.ts (1)

109-116: ⚠️ Potential issue | 🟠 Major

Validate wallet address before using .ilike

.ilike uses SQL‐style pattern matching (% and _ are wildcards). Since query.address is unvalidated, a value containing % or _ could match multiple records. Enforce the expected 0x-prefixed hex format (or escape wildcards) before querying.

🔧 Suggested fix
   if (query.address) {
     const address = Array.isArray(query.address) ? query.address[0] : query.address;
+    if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
+      return res.status(400).json({
+        result: "error",
+        error: { message: "Invalid wallet address" },
+      });
+    }
     const supabase = createSupabaseServerClient();
     const { data, error } = await supabase
       .from("walletIdentities")
       .select()
       // wallet addresses may be checksummed; match case-insensitively
       .ilike("walletAddress", address);
src/fidgets/farcaster/index.tsx (1)

56-139: ⚠️ Potential issue | 🟠 Major

Honor authenticatorName throughout the hook.
ensureSigner, the installed check, and FID lookups are hard‑coded to FARCASTER_AUTHENTICATOR_NAME, so passing a non‑default authenticatorName won’t work. Use the parameter consistently.

🔧 Suggested fix
-  const { setModalOpen, isAuthenticatorInstalled } = useAppStore((state) => ({
-    setModalOpen: state.setup.setModalOpen,
-    isAuthenticatorInstalled: !!state.account.authenticatorConfig[FARCASTER_AUTHENTICATOR_NAME],
-  }));
+  const { setModalOpen, authenticatorConfig } = useAppStore((state) => ({
+    setModalOpen: state.setup.setModalOpen,
+    authenticatorConfig: state.account.authenticatorConfig,
+  }));
+  const isAuthenticatorInstalled = !!authenticatorConfig[authenticatorName];
@@
-    if (!isAuthenticatorInstalled) {
-      await authenticatorManager.installAuthenticators([FARCASTER_AUTHENTICATOR_NAME]);
-      authenticatorManager.initializeAuthenticators([FARCASTER_AUTHENTICATOR_NAME]);
-    } else {
-      authenticatorManager.initializeAuthenticators([FARCASTER_AUTHENTICATOR_NAME]);
-    }
+    if (!isAuthenticatorInstalled) {
+      await authenticatorManager.installAuthenticators([authenticatorName]);
+      authenticatorManager.initializeAuthenticators([authenticatorName]);
+    } else {
+      authenticatorManager.initializeAuthenticators([authenticatorName]);
+    }
@@
-      const ready = await waitForAuthenticatorReady(authenticatorManager, FARCASTER_AUTHENTICATOR_NAME);
+      const ready = await waitForAuthenticatorReady(authenticatorManager, authenticatorName);
@@
-      .then((initilizedAuths) => setHasSigner(indexOf(initilizedAuths, FARCASTER_AUTHENTICATOR_NAME) !== -1));
-  }, [authenticatorManager.lastUpdatedAt]);
+      .then((initilizedAuths) => setHasSigner(indexOf(initilizedAuths, authenticatorName) !== -1));
+  }, [authenticatorManager.lastUpdatedAt, authenticatorName]);
@@
-        authenticatorId: FARCASTER_AUTHENTICATOR_NAME,
+        authenticatorId: authenticatorName,
@@
-  }, [authenticatorManager.lastUpdatedAt, hasSigner, authenticatorManager, fidgetId, currentFid]);
+  }, [authenticatorManager.lastUpdatedAt, hasSigner, authenticatorManager, fidgetId, currentFid, authenticatorName]);
src/authenticators/AuthenticatorManager.tsx (2)

249-305: ⚠️ Potential issue | 🟠 Major

Use a functional update when completing the initializer queue.

completeInstallingCurrentInitializer captures initializationQueue from its closure, which can become stale. When the function is passed to CurrentInitializerComponent and called asynchronously, it may reference an outdated queue value, potentially dropping items if the queue changed since the component was rendered.

🔧 Suggested fix
   function completeInstallingCurrentInitializer() {
-    setInitializationQueue(tail(initializationQueue));
+    setInitializationQueue((queue) => tail(queue));
     setCurrentInitializer(undefined);
   }

165-272: ⚠️ Potential issue | 🟠 Major

Include modalOpen, currentStep, and setModalOpen in the memo dependencies to prevent stale gating logic.
callMethod evaluates !modalOpen && !isLookup && currentStep === SetupStep.DONE to conditionally call setModalOpen, but the memoized authenticatorManager captures these values only once at creation. When setup state changes, the closure continues using stale values, causing the modal auto-open guard to behave incorrectly during setup transitions.

🔧 Suggested fix
-    [
-      authenticatorConfig,
-      installedAuthenticators,
-      currentInitializer,
-      identityPublicKey,
-      walletAddress,
-    ],
+    [
+      authenticatorConfig,
+      installedAuthenticators,
+      currentInitializer,
+      identityPublicKey,
+      walletAddress,
+      modalOpen,
+      currentStep,
+      setModalOpen,
+    ],
src/pages/api/fid-link.ts (2)

41-82: ⚠️ Potential issue | 🟠 Major

Distinguish Neynar lookup failures from invalid keys to enable proper retries.

Catching and suppressing the lookup error masks transient failures as invalid keys (400), blocking legitimate requests during Neynar outages. The codebase pattern (seen in publishMessage.ts and getFids.ts) is to propagate service errors as 5xx responses. Refactor to throw from checkSigningKeyValidForFid() on lookup failure, wrap the call at line 74 in try-catch, and return 503 Service Unavailable for lookup failures while preserving 400 for genuinely invalid keys.

🔧 Suggested adjustment
 async function checkSigningKeyValidForFid(fid: number, signingKey: string) {
   try {
-    const result = await neynar.lookupDeveloperManagedSigner({publicKey: signingKey});
+    const result = await neynar.lookupDeveloperManagedSigner({ publicKey: signingKey });
     return result.fid === fid && result.status === "approved";
-  } catch {
-    return false;
+  } catch (err) {
+    throw new Error("signer_lookup_failed");
   }
 }
@@
-  if (!(await checkSigningKeyValidForFid(reqBody.fid, reqBody.signingPublicKey))) {
-    res.status(400).json({
-      result: "error",
-      error: {
-        message: `Signing key ${reqBody.signingPublicKey} is not valid for fid ${reqBody.fid}`,
-      },
-    });
-    return;
-  }
+  let isSigningKeyValid: boolean;
+  try {
+    isSigningKeyValid = await checkSigningKeyValidForFid(
+      reqBody.fid,
+      reqBody.signingPublicKey,
+    );
+  } catch {
+    res.status(503).json({
+      result: "error",
+      error: { message: "Signer lookup failed. Please retry." },
+    });
+    return;
+  }
+  if (!isSigningKeyValid) {
+    res.status(400).json({
+      result: "error",
+      error: {
+        message: `Signing key ${reqBody.signingPublicKey} is not valid for fid ${reqBody.fid}`,
+      },
+    });
+    return;
+  }

173-177: ⚠️ Potential issue | 🟠 Major

Add filtering by isSigningKeyValid or include validation status in response.

This query returns all FIDs regardless of signature validity. The /infer endpoint intentionally creates records with isSigningKeyValid: false to enable space editing without a validated signing key, but Farcaster Write Actions should fail. The existing TODO comment ("// TO DO: Refresh that these signatures are valid") confirms this is incomplete work.

Either:

  1. Filter the query to include only .eq("isSigningKeyValid", true) if all callers require validated FIDs, or
  2. Include isSigningKeyValid in the response so callers can distinguish between validated and unvalidated FIDs and act accordingly

Currently, downstream callers like getFidsForCurrentIdentity() receive the mixed list without context on which FIDs are validated.

src/fidgets/farcaster/Top8.tsx (1)

23-33: ⚠️ Potential issue | 🟠 Major

Fallback username is still applied when settings.username is missing.
username still defaults to "nounspacetom" (and the field default remains the same), so missing settings will continue to render the default profile. The trimmed/empty check only helps when the user explicitly clears the field.

🛠️ Suggested fix to avoid default fallback
   fields: [
     {
       fieldName: "username",
       displayName: "Farcaster Username",
-      default: "nounspacetom",
+      default: "",
       required: true,
       inputSelector: (props) => (
         <WithMargin>
           <TextInput {...props} />
         </WithMargin>
       ),
       group: "settings",
     },
   const {
-    username = "nounspacetom",
+    username = "",
     size = 0.6,
     background,
     fidgetBorderColor,
     fidgetBorderWidth,
     fidgetShadow,
   } = settings;

Also applies to: 54-68

src/fidgets/farcaster/components/CreateCast.tsx (1)

415-439: ⚠️ Potential issue | 🟡 Minor

Avoid prompting signer flow before draft validation.
getOrCreateSigner() runs before the empty-draft and mention-resolution checks, which can open the Farcaster modal even when submission should be rejected. Consider validating first, then acquiring a signer.

Proposed fix
-  const activeSigner = signer ?? (await getOrCreateSigner());
-  if ((!draft?.text && !draft?.embeds?.length) || !activeSigner) {
+  if (!draft?.text && !draft?.embeds?.length) {
     console.error(
-      "Submission failed: Missing text or embeds, or signer is undefined.",
+      "Submission failed: Missing text or embeds.",
       {
         draftText: draft?.text,
         draftEmbedsLength: draft?.embeds?.length,
-        signerUndefined: !activeSigner,
       },
     );
     return false;
   }
@@
-  try {
-    const result = await publishPost(draft, fid, activeSigner);
+  const activeSigner = signer ?? (await getOrCreateSigner());
+  if (!activeSigner) {
+    console.error("Submission failed: signer is undefined.");
+    return false;
+  }
+  try {
+    const result = await publishPost(draft, fid, activeSigner);
src/common/data/stores/app/space/spaceStore.ts (1)

247-255: ⚠️ Potential issue | 🟡 Minor

Guard localSpaces[spaceId] before accessing .tabs.
Add a null check for get().space.localSpaces[spaceId] to avoid dereferencing undefined.

Proposed fix
-  const localCopy = cloneDeep(
-    get().space.localSpaces[spaceId].tabs[tabName],
-  );
-  if (!localCopy) {
+  const localSpace = get().space.localSpaces[spaceId];
+  const localCopy = localSpace ? cloneDeep(localSpace.tabs[tabName]) : undefined;
+  if (!localCopy) {
     console.warn(`Tab ${tabName} not found in localSpaces for space ${spaceId}`);
     return;
   }
🤖 Fix all issues with AI agents
In `@src/app/`(spaces)/PublicSpace.tsx:
- Around line 254-259: The registration path can call registerSpaceFid with an
empty space name; update the isProfileSpace branch to guard that
spacePageData.spaceName is non-empty (truthy) before calling registerSpaceFid
(and also ensure currentUserFid is present), otherwise skip/delay registration
until the profile name is available; reference the isProfileSpace check,
spacePageData.spaceName, spacePageData.spacePageUrl, currentUserFid, and
registerSpaceFid when making the change.

In
`@src/authenticators/farcaster/signers/NounspaceManagedSignerAuthenticator.tsx`:
- Around line 228-240: The devSignin flow calls saveData(...) and immediately
calls done(), which can complete initialization before the async saveData
finishes; change devSignin to await the async saveData call (i.e., make
devSignin async if not already) and only call done() after saveData resolves
(handle/rethrow or log errors from saveData to preserve behavior). Ensure you
update the devSignin function signature and await saveData(...) before invoking
done().

In `@src/pages/api/fid-link/infer.ts`:
- Around line 194-212: The insert into fidRegistrations using
supabase.from("fidRegistrations").insert(...) can fail with duplicate-key errors
on concurrent/retried calls; update the handler so that when insertError
indicates a unique-constraint/duplicate-key violation you treat it as success:
catch that specific error from the insert call (check insertError.code/message),
then re-query fidRegistrations for the existing row by inferredFid (and
identityPublicKey) to retrieve the record, and return that record in the same
successful response path instead of returning a 500; keep the original error
handling for other error types.

Comment on lines 254 to 259
} else if (isProfileSpace(spacePageData) && !isNil(currentUserFid)) {
newSpaceId = await registerSpaceFid(
currentUserFid,
spacePageData.defaultTab,
spacePageData.spaceName,
spacePageData.spacePageUrl(spacePageData.defaultTab),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against missing profile space names.
If spacePageData.spaceName can be empty while the profile data loads, registration will persist an empty name. Consider delaying registration until it’s available.

Proposed fix
-  } else if (isProfileSpace(spacePageData) && !isNil(currentUserFid)) {
-    newSpaceId = await registerSpaceFid(
-      currentUserFid,
-      spacePageData.spaceName,
-      spacePageData.spacePageUrl(spacePageData.defaultTab),
-    );
-  } else if (isChannelSpace(spacePageData) && !isNil(currentUserFid)) {
+  } else if (isProfileSpace(spacePageData) && !isNil(currentUserFid)) {
+    if (!spacePageData.spaceName) {
+      console.warn("Profile space name not resolved yet; delaying registration.");
+      return;
+    }
+    newSpaceId = await registerSpaceFid(
+      currentUserFid,
+      spacePageData.spaceName,
+      spacePageData.spacePageUrl(spacePageData.defaultTab),
+    );
+  } else if (isChannelSpace(spacePageData) && !isNil(currentUserFid)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (isProfileSpace(spacePageData) && !isNil(currentUserFid)) {
newSpaceId = await registerSpaceFid(
currentUserFid,
spacePageData.defaultTab,
spacePageData.spaceName,
spacePageData.spacePageUrl(spacePageData.defaultTab),
);
} else if (isProfileSpace(spacePageData) && !isNil(currentUserFid)) {
if (!spacePageData.spaceName) {
console.warn("Profile space name not resolved yet; delaying registration.");
return;
}
newSpaceId = await registerSpaceFid(
currentUserFid,
spacePageData.spaceName,
spacePageData.spacePageUrl(spacePageData.defaultTab),
);
} else if (isChannelSpace(spacePageData) && !isNil(currentUserFid)) {
🤖 Prompt for AI Agents
In `@src/app/`(spaces)/PublicSpace.tsx around lines 254 - 259, The registration
path can call registerSpaceFid with an empty space name; update the
isProfileSpace branch to guard that spacePageData.spaceName is non-empty
(truthy) before calling registerSpaceFid (and also ensure currentUserFid is
present), otherwise skip/delay registration until the profile name is available;
reference the isProfileSpace check, spacePageData.spaceName,
spacePageData.spacePageUrl, currentUserFid, and registerSpaceFid when making the
change.

Comment on lines 228 to 240
function devSignin() {
// In development, generate test signing keys so Quick Auth can work
// These are random keys - not linked to a real Farcaster account
const newPrivKey = ed25519.utils.randomPrivateKey();
const publicKeyHex = `0x${bytesToHex(ed25519.getPublicKey(newPrivKey))}`;
const privateKeyHex = `0x${bytesToHex(newPrivKey)}`;

saveData({
...data,
status: "completed",
accountFid: Number(devFid),
accountType: "signer", // Use signer type so keys are available
publicKeyHex: publicKeyHex,
privateKeyHex: privateKeyHex,
accountType: "account",
publicKeyHex,
privateKeyHex: `0x${bytesToHex(newPrivKey)}`,
});
done();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Await saveData before calling done().
saveData is async in other paths; calling done() immediately can mark initialization complete before keys persist.

✅ Suggested fix
-  function devSignin() {
+  async function devSignin() {
     const newPrivKey = ed25519.utils.randomPrivateKey();
     const publicKeyHex = `0x${bytesToHex(ed25519.getPublicKey(newPrivKey))}`;
-    saveData({
+    await saveData({
       ...data,
       status: "completed",
       accountFid: Number(devFid),
       accountType: "account",
       publicKeyHex,
       privateKeyHex: `0x${bytesToHex(newPrivKey)}`,
     });
     done();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function devSignin() {
// In development, generate test signing keys so Quick Auth can work
// These are random keys - not linked to a real Farcaster account
const newPrivKey = ed25519.utils.randomPrivateKey();
const publicKeyHex = `0x${bytesToHex(ed25519.getPublicKey(newPrivKey))}`;
const privateKeyHex = `0x${bytesToHex(newPrivKey)}`;
saveData({
...data,
status: "completed",
accountFid: Number(devFid),
accountType: "signer", // Use signer type so keys are available
publicKeyHex: publicKeyHex,
privateKeyHex: privateKeyHex,
accountType: "account",
publicKeyHex,
privateKeyHex: `0x${bytesToHex(newPrivKey)}`,
});
done();
}
async function devSignin() {
const newPrivKey = ed25519.utils.randomPrivateKey();
const publicKeyHex = `0x${bytesToHex(ed25519.getPublicKey(newPrivKey))}`;
await saveData({
...data,
status: "completed",
accountFid: Number(devFid),
accountType: "account",
publicKeyHex,
privateKeyHex: `0x${bytesToHex(newPrivKey)}`,
});
done();
}
🤖 Prompt for AI Agents
In `@src/authenticators/farcaster/signers/NounspaceManagedSignerAuthenticator.tsx`
around lines 228 - 240, The devSignin flow calls saveData(...) and immediately
calls done(), which can complete initialization before the async saveData
finishes; change devSignin to await the async saveData call (i.e., make
devSignin async if not already) and only call done() after saveData resolves
(handle/rethrow or log errors from saveData to preserve behavior). Ensure you
update the devSignin function signature and await saveData(...) before invoking
done().

Comment on lines +194 to +212
// Persist the inferred link even if no Farcaster Signer is present.
// This allows the server to recognize the Identity Key as a valid owner of the Space (linked to the FID),
// enabling Space Editing to proceed. Farcaster Write Actions (Casting) will still fail or prompt for a signer
// because isSigningKeyValid is false.
const { error: insertError } = await supabase
.from("fidRegistrations")
.insert({
fid: inferredFid,
identityPublicKey: body.identityPublicKey,
created: serverNow,
isSigningKeyValid: false,
signingPublicKey: null,
signature: null,
signingKeyLastValidatedAt: null,
});

if (insertError) {
return res.status(500).json({ result: "error", error: { message: insertError.message } });
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the insert path idempotent to avoid duplicate-key 500s.
Concurrent/retried calls for the same FID can cause the insert to fail and block signup. Consider treating duplicate inserts as success by re-reading the existing row and returning it.

🛠️ Suggested handling for duplicate insert retries
   const { error: insertError } = await supabase
     .from("fidRegistrations")
     .insert({
       fid: inferredFid,
       identityPublicKey: body.identityPublicKey,
       created: serverNow,
       isSigningKeyValid: false,
       signingPublicKey: null,
       signature: null,
       signingKeyLastValidatedAt: null,
     });

   if (insertError) {
-    return res.status(500).json({ result: "error", error: { message: insertError.message } });
+    const { data: existingAfter, error: refetchError } = await supabase
+      .from("fidRegistrations")
+      .select("fid, created, identityPublicKey")
+      .eq("fid", inferredFid)
+      .maybeSingle();
+    if (!refetchError && existingAfter) {
+      return res.status(200).json({
+        result: "success",
+        value: {
+          fid: existingAfter.fid,
+          identityPublicKey: existingAfter.identityPublicKey,
+          created: existingAfter.created,
+          inferredFromAddress: walletAddress,
+        },
+      });
+    }
+    return res.status(500).json({ result: "error", error: { message: insertError.message } });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Persist the inferred link even if no Farcaster Signer is present.
// This allows the server to recognize the Identity Key as a valid owner of the Space (linked to the FID),
// enabling Space Editing to proceed. Farcaster Write Actions (Casting) will still fail or prompt for a signer
// because isSigningKeyValid is false.
const { error: insertError } = await supabase
.from("fidRegistrations")
.insert({
fid: inferredFid,
identityPublicKey: body.identityPublicKey,
created: serverNow,
isSigningKeyValid: false,
signingPublicKey: null,
signature: null,
signingKeyLastValidatedAt: null,
});
if (insertError) {
return res.status(500).json({ result: "error", error: { message: insertError.message } });
}
// Persist the inferred link even if no Farcaster Signer is present.
// This allows the server to recognize the Identity Key as a valid owner of the Space (linked to the FID),
// enabling Space Editing to proceed. Farcaster Write Actions (Casting) will still fail or prompt for a signer
// because isSigningKeyValid is false.
const { error: insertError } = await supabase
.from("fidRegistrations")
.insert({
fid: inferredFid,
identityPublicKey: body.identityPublicKey,
created: serverNow,
isSigningKeyValid: false,
signingPublicKey: null,
signature: null,
signingKeyLastValidatedAt: null,
});
if (insertError) {
const { data: existingAfter, error: refetchError } = await supabase
.from("fidRegistrations")
.select("fid, created, identityPublicKey")
.eq("fid", inferredFid)
.maybeSingle();
if (!refetchError && existingAfter) {
return res.status(200).json({
result: "success",
value: {
fid: existingAfter.fid,
identityPublicKey: existingAfter.identityPublicKey,
created: existingAfter.created,
inferredFromAddress: walletAddress,
},
});
}
return res.status(500).json({ result: "error", error: { message: insertError.message } });
}
🤖 Prompt for AI Agents
In `@src/pages/api/fid-link/infer.ts` around lines 194 - 212, The insert into
fidRegistrations using supabase.from("fidRegistrations").insert(...) can fail
with duplicate-key errors on concurrent/retried calls; update the handler so
that when insertError indicates a unique-constraint/duplicate-key violation you
treat it as success: catch that specific error from the insert call (check
insertError.code/message), then re-query fidRegistrations for the existing row
by inferredFid (and identityPublicKey) to retrieve the record, and return that
record in the same successful response path instead of returning a 500; keep the
original error handling for other error types.

@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
space-system Ready Ready Preview, Comment Feb 4, 2026 3:14pm

… Top8 by default when no specific username is provided.
…user and introduce a `disableSettingsBackfill` property for fidgets.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/fidgets/farcaster/Top8.tsx`:
- Around line 111-115: The effectiveUsername computation still includes the
hardcoded "nounspacetom" and redundant branches; update the logic in the
effectiveUsername assignment to remove the hardcoded fallback and redundant
checks so it yields either normalizedCurrentUsername when shouldUseLoggedIn is
true, or normalizedSettingsUsername otherwise, and default to an empty string if
neither exists (e.g., effectiveUsername = shouldUseLoggedIn ?
normalizedCurrentUsername || "" : normalizedSettingsUsername || ""); keep
iframeUrl's existing handling of empty usernames intact.
🧹 Nitpick comments (1)
src/fidgets/farcaster/Top8.tsx (1)

101-106: Consider extracting the username derivation logic to avoid duplication.

Both Top8UsernameInput (lines 30-35) and the main Top8 component (lines 101-106) make identical useCurrentFid() and useLoadFarcasterUser() calls. While React Query's caching mitigates redundant network requests, extracting this into a custom hook would improve maintainability.

Example custom hook
const useCurrentUsername = () => {
  const currentFid = useCurrentFid();
  const { data } = useLoadFarcasterUser(currentFid ?? -1);
  return useMemo(
    () => data?.users?.[0]?.username?.trim(),
    [data],
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant