Skip to content

feat: Add Farcaster miniapp support for identity verification flow #10 - #14

Merged
L03TJ3 merged 45 commits into
GoodDollar:finalize-farcaster-supportfrom
vortex-hue:add-farcaster-support-for-the-identity-flow
Jul 14, 2026
Merged

L03TJ3 merged 45 commits into
GoodDollar:finalize-farcaster-supportfrom
vortex-hue:add-farcaster-support-for-the-identity-flow

Conversation

@vortex-hue

@vortex-hue vortex-hue commented Aug 14, 2025 •

Copy link
Copy Markdown

Description

This PR implements comprehensive Farcaster miniapp support for the identity verification flow, enabling seamless face verification within Farcaster miniapps with proper navigation and universal link handling. The implementation includes automatic miniapp detection, smart navigation, and robust response handling while fixing critical build system issues that were preventing successful compilation.

The changes address the need for better Farcaster miniapp integration by implementing the official @farcaster/miniapp-sdk, adding universal link support for mobile/native compatibility, and creating utilities for handling post-verification flow continuation. Additionally, this PR resolves significant build configuration issues including Node.js dependency conflicts in browser builds and security vulnerabilities related to environment variable exposure.

Dependencies added:

  • @farcaster/miniapp-sdk (peer dependency)
  • Browser polyfills for Node.js modules (crypto-browserify, stream-browserify, buffer)

About #10

Browser Demo Video: Loom GoodSDK Forecaster Support

Mobile Demo Video: Loom GoodSDK Forecaster Support

I would like to know how I can demo on a mobile please, but I was able to test the demo on a browser.

Checklist:

Key Implementation Details:

Farcaster Integration:

  • isInFarcasterMiniApp() - Async detection using official SDK
  • navigateToFaceVerification() - Smart navigation with automatic environment detection
  • openUrlInFarcaster() - Official SDK integration with openUrl method
  • handleVerificationResponse() - Generic utility for post-verification flow
  • createUniversalLinkCallback() - Universal link support for mobile/native apps

Build System Fixes:

  • Externalized Node.js modules (crypto, http, https, stream, zlib, etc.) for browser compatibility
  • Fixed security vulnerabilities by replacing full process.env exposure with specific variables
  • Optimized tsup configuration for ESM-only builds to avoid IIFE conflicts
  • Restructured package dependencies to use peer dependencies for heavy libraries

Sensitive Files Modified:

These are some of the files I modified after my implementation due to build error I was getting, because it was building for browser, meanwhile some Node.js functions are also present, I'd appreciate if there's another way I could resolve the error without modifying these files to avoid a merge conflict.

  • Core SDK: packages/citizen-sdk/src/index.ts, packages/citizen-sdk/src/utils/auth.ts
  • Build configs: packages/ui-components/tsup.config.claim.ts, packages/ui-components/package.json
  • Demo apps: apps/demo-identity-app/vite.config.mts, apps/demo-webcomponents/vite.config.mts
  • Components: apps/demo-identity-app/src/components/VerifyButton.tsx
  • Types: apps/demo-identity-app/src/globals.d.ts
  • yarn.lock - Updated dependencies

@korbit-ai

korbit-ai Bot commented Aug 14, 2025

Copy link
Copy Markdown

You've used up your 5 PR reviews for this month under the Korbit Starter Plan. You'll get 5 more reviews on August 19th, 2025 or you can upgrade to Pro for unlimited PR reviews and enhanced features in your Korbit Console.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Extract the repeated list of externalized Node.js modules into a shared build config or utility to avoid duplication across your Vite and tsup configurations.
  • Cache or hoist the dynamic import of '@farcaster/miniapp-sdk' so you’re not re-importing it multiple times during fallback detection and navigation.
  • Refactor the universal link generation and Farcaster navigation logic into a single shared helper or custom hook to eliminate duplication across IdentitySDKs and the demo app.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Extract the repeated list of externalized Node.js modules into a shared build config or utility to avoid duplication across your Vite and tsup configurations.
- Cache or hoist the dynamic import of '@farcaster/miniapp-sdk' so you’re not re-importing it multiple times during fallback detection and navigation.
- Refactor the universal link generation and Farcaster navigation logic into a single shared helper or custom hook to eliminate duplication across IdentitySDKs and the demo app.

## Individual Comments

### Comment 1
<location> `packages/citizen-sdk/src/utils/auth.ts:94` </location>
<code_context>
+ * @param url - The current URL or callback URL to parse
+ * @returns Object containing verification status and any additional parameters
+ */
+export function handleVerificationResponse(url?: string): {
+  isVerified: boolean;
+  params: URLSearchParams;
</code_context>

<issue_to_address>
Parsing the URL without error handling may throw if the input is malformed.

If parsing fails due to an invalid URL, the function will throw. Please add a try/catch block and return a default value when parsing fails.
</issue_to_address>

### Comment 2
<location> `packages/citizen-sdk/src/utils/auth.ts:7` </location>
<code_context>
+/**
+ * Detects if the SDK is running inside a Farcaster miniapp using the official SDK
+ */
+export async function isInFarcasterMiniApp(timeoutMs: number = 100): Promise<boolean> {
+  if (typeof window === "undefined") return false;
+  
</code_context>

<issue_to_address>
Consider refactoring repeated dynamic imports and fallback logic into helper functions to simplify and flatten the code structure.

Here’s one way to collapse all of the repeated dynamic‐imports, pull the fallback logic out into a small helper, and flatten the nesting in isInFarcasterMiniApp / openUrlInFarcaster:

```ts
// new helper to lazy-load & cache the SDK
let _cachedSdk: typeof import('@farcaster/miniapp-sdk').sdk | null = null;
async function loadFarcasterSdk() {
  if (!_cachedSdk) {
    const { sdk } = await import('@farcaster/miniapp-sdk');
    _cachedSdk = sdk;
  }
  return _cachedSdk;
}

// new helper for the fallback context check
async function fallbackDetect() {
  try {
    const sdk = await loadFarcasterSdk();
    const ctx = await sdk.context;
    return !!(ctx.location && ctx.location.type != null);
  } catch {
    return false;
  }
}

export async function isInFarcasterMiniApp(timeoutMs = 100): Promise<boolean> {
  if (typeof window === 'undefined') return false;
  try {
    const sdk = await loadFarcasterSdk();
    return await sdk.isInMiniApp();
  } catch {
    console.warn('SDK failed, trying context fallback…');
    return fallbackDetect();
  }
}

export async function openUrlInFarcaster(
  url: string,
  fallbackToNewTab = true
): Promise<void> {
  if (typeof window === 'undefined') {
    throw new Error('Browser only');
  }

  if (await isInFarcasterMiniApp()) {
    try {
      const sdk = await loadFarcasterSdk();
      await sdk.actions.ready();
      await sdk.actions.openUrl(url);
      return;
    } catch {
      console.warn('SDK.openUrl failed, falling back…');
    }
  }

  fallbackToNewTab ? window.open(url, '_blank') : (window.location.href = url);
}
```

Benefits:

• All `import('@farcaster/miniapp-sdk')` calls are in one place and cached  
• Fallback context logic lives in its own small function  
• Try/catch blocks are only one level deep in each API function  
• Behaviour is unchanged.
</issue_to_address>

### Comment 3
<location> `apps/demo-identity-app/vite.config.mts:37` </location>
<code_context>
   },
+  build: {
+    rollupOptions: {
+      external: [
+        // External Node.js modules that should not be bundled for browser
+        "crypto",
</code_context>

<issue_to_address>
Consider generating the list of Node.js built-ins and their globals dynamically using module.builtinModules instead of maintaining a manual array.

You can eliminate the manual list entirely by pulling Node’s built-ins from `module.builtinModules` (and generate the `node:`-prefixed variants on the fly). For example:

```js
// vite.config.js
import { builtinModules } from 'module'

const builtins = [
  ...builtinModules,
  ...builtinModules.map((m) => `node:${m}`)
]

const globals = builtins.reduce((acc, name) => {
  // strip the `node:` prefix for the global key if present
  const key = name.startsWith('node:') ? name.slice(5) : name
  // map both `foo` and `node:foo` → global `foo`
  acc[name] = key
  return acc
}, {})

export default defineConfig({
  // …other config…
  build: {
    rollupOptions: {
      external: builtins,
      output: { globals }
    }
  }
})
```

This keeps every built-in, automatically picks up new ones, and avoids the long hand-written arrays.
</issue_to_address>

### Comment 4
<location> `packages/ui-components/tsup.config.claim.ts:16` </location>
<code_context>
   },
+  build: {
+    rollupOptions: {
+      external: [
+        // External Node.js modules that should not be bundled for browser
+        "crypto",
</code_context>

<issue_to_address>
Consider dynamically listing Node.js built-ins or using a regex to avoid manually maintaining a long list of external dependencies.

You can drop the hundreds-of-lines hand–listing by pulling Node’s built-ins dynamically from the `module` package (and catching both plain and `node:` names). For example:

```ts
// tsup.config.ts
import { defineConfig } from "tsup"
import { builtinModules } from "module"

const nodeBuiltins = [
  ...builtinModules,              // ['fs','path',…]
  ...builtinModules.map((m) => `node:${m}`), // ['node:fs','node:path',…]
]

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm"],
  platform: "browser",
  globalName: "ClaimButton",
  splitting: false,
  sourcemap: false,
  clean: true,
  dts: false,
  minify: true,
  target: "ESNext",
  outDir: "dist",
  external: [
    "@goodsdks/citizen-sdk",
    "viem",
    ...nodeBuiltins,
  ],
  noExternal: [
    "lit",
    "@reown/appkit",
    "@reown/appkit-adapter-ethers",
    "ethers",
  ],
})
```

If you’d rather use a regex to catch all `node:` imports and leave plain built-ins implicit, you can shrink it even more:

```ts
export default defineConfig({
  // …
  external: [
    "@goodsdks/citizen-sdk",
    "viem",
    /^node:/,          // all `node:xxx`
    // (esbuild will already treat bare built-ins as external in browser builds)
  ],
  // …
})
```

Either approach removes the manual maintenance burden while keeping the same behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
Comment thread apps/demo-identity-app/vite.config.mts Outdated
Comment thread packages/ui-components/tsup.config.claim.ts Outdated
Comment thread packages/citizen-sdk/src/sdks/viem-identity-sdk.ts Outdated
Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
@vortex-hue

vortex-hue commented Aug 14, 2025 •

Copy link
Copy Markdown
Author

review changes by sorcery-ai implemented

@L03TJ3 L03TJ3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I also think I miss seeing handling of the 'verified' param that you get when returning from the face-verification?

Comment thread packages/ui-components/tsup.config.claim.ts
Comment thread packages/ui-components/package.json
Comment thread packages/build-config/index.ts Outdated
Comment thread apps/demo-webcomponents/vite.config.mts
Comment thread packages/citizen-sdk/src/sdks/viem-identity-sdk.ts Outdated
Comment thread packages/citizen-sdk/src/sdks/viem-identity-sdk.ts Outdated
@vortex-hue

Copy link
Copy Markdown
Author

I also think I miss seeing handling of the 'verified' param that you get when returning from the face-verification?

The verified param from face verification is already properly handled in the demo-identity-app (App.tsx lines 59-71) using the handleVerificationResponse() utility function.

@vortex-hue

Copy link
Copy Markdown
Author

also, I'd ⁠work on building a test mini-app on farcaster, and demonstrate how it works

@sirpy sirpy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. The code needs to be organized better. Many duplicates, single line functions etc.
  2. since farcaster always opens a new tab/window it is possibly best to force cbu, ie popup mode, where the tab/window will be closed after FV is done without redirect back.
  3. for cbu need to update the check is verified, to not only check the url for isVerified param (since there is no redirect with that param in cbu mode) but also to accept as input from developer the wallet address and check on-chain if wallet is now whitelisted

Comment thread apps/demo-identity-app/package.json Outdated
"format": "prettier --write ."
},
"dependencies": {
"@farcaster/miniapp-sdk": "^0.1.8",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should be devdep/peerdep

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@vortex-hue this is not done

Comment thread apps/demo-identity-app/vite.config.mts Outdated
Comment thread apps/demo-identity-app/vite.config.mts Outdated
Comment thread apps/demo-identity-app/vite.config.mts Outdated
Comment thread apps/demo-webcomponents/vite.config.mts Outdated
Comment on lines +31 to +35
build: {
rollupOptions: {
external: ["@goodsdks/citizen-sdk"]
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

doesnt make sense this has to be included

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This external configuration is required to prevent the citizen-sdk (which contains Node.js dependencies) from being bundled in the browser environment. Without this, the build fails with 'Agent is not exported by vite-browser-external' errors. The citizen-sdk needs to remain external for this demo app.

/**
* Create a universal link compatible callback URL
*/
private createUniversalLinkCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

duplicate code

url: string,
fallbackToNewTab: boolean = true
): Promise<void> {
if (typeof window === "undefined") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

duplicate code. if in farcaster then it is obvious theres a window this check is redundant

Comment thread packages/ui-components/tsup.config.claim.ts Outdated
Comment thread packages/ui-components/tsup.config.claim.ts Outdated
Comment thread packages/ui-components/tsup.config.claim.ts Outdated
@sirpy

sirpy commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

@L03TJ3 the non custodial is just a duplicate code requiring now multiple maintenance. it should be integrated into the regular sdk

@vortex-hue

vortex-hue commented Aug 23, 2025 •

Copy link
Copy Markdown
Author

Hi @L03TJ3 @sirpy, I've implemented all the corrections, but am yet to still understand how to test it in mini app, should I turn the example test app into a forecaster mini app?

@sirpy

sirpy commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Hi @L03TJ3 @sirpy, I've implemented all the corrections, but am yet to still understand how to test it in mini app, should I turn the example test app into a forecaster mini app?

either it should work in both (web/farcaster) or you can create a similar farcaster miniapp

@vortex-hue

Copy link
Copy Markdown
Author

Hi @sirpy @L03TJ3 , I've been able to test it in web/farcaster environments, here's the loom: Demo in Forecaster & Web, also thanks Lewis for other time, and Emiri helped me alot too

@vortex-hue

Copy link
Copy Markdown
Author

Do let me know if there's anything else pending, thanks alot.

@vortex-hue

Copy link
Copy Markdown
Author

Hi @L03TJ3 have you gotten time to review this pr and also probably watch the loom, so I can complete this task.

@sirpy

sirpy commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

@vortex-hue Please make sure you went over all the open items and made the relevant changes.
The loom looks nice! can you also test it on the mobile farcaster app?

@L03TJ3 should review this by end of week

@vortex-hue

Copy link
Copy Markdown
Author

@vortex-hue Please make sure you went over all the open items and made the relevant changes. The loom looks nice! can you also test it on the mobile farcaster app?

@L03TJ3 should review this by end of week

Hi @sirpy, I went through all open items and also made the changes requested, is there any specific one I missed? and sure, let me try testing it on mobile right away.

Comment thread packages/citizen-sdk/src/sdks/viem-custodial-identity-sdk.ts Outdated
Comment thread apps/demo-webcomponents/vite.config.mts
Comment thread apps/demo-identity-app/vite.config.mts Outdated
Comment thread packages/citizen-sdk/src/sdks/viem-claim-sdk.ts Outdated
Comment thread packages/citizen-sdk/src/sdks/viem-claim-sdk.ts Outdated
Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
Comment on lines +56 to +59
window.location.href.includes("farcaster") ||
window.location.href.includes("miniapp") ||
// Check if we're in an iframe which is common for miniapps
window.self !== window.top

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
Comment on lines +72 to +74
if (typeof window === "undefined") {
throw new Error("URL opening is only supported in browser environments.");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
* @param additionalParams - Additional parameters to include
* @returns A universal link compatible URL
*/
export function createUniversalLinkCallback(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I requested this flow so that the verified param can be appened and a app can handle the redirectBack from face-verification.
You think this should be handled different?

Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
* @param additionalParams - Additional parameters to include
* @returns A universal link compatible URL
*/
export function createUniversalLinkCallback(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@vercel

vercel Bot commented Sep 2, 2025

Copy link
Copy Markdown

Deployment failed with the following error:

The provided GitHub repository does not contain the requested branch or commit reference. Please ensure the repository is not empty.

… also created packages/build-config/index.ts with dynamic Node.js built-ins generation
console.error("submitAndWait Error:", error)
throw new Error(`Failed to submit transaction: ${error.message}`)
}
return waitForTransactionReceipt(this.publicClient, { hash })

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@vortex-hue comment not addresses

);
params[popupMode ? "cbu" : "rdu"] = universalLinkCallback;
} else {
const callbackUrlWithParams = await createVerificationCallbackUrl(callbackUrl, {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@vortex-hue comment not addressed

* @param authPeriod - The authentication period.
* @returns The identity expiry data.
*/
calculateIdentityExpiry(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We write claritive comments to explain to anyone reading it what it does.
removing comments is not part of your task or is contributing to anything.
revert changes towards it.

your pull-request should only tackle what is described in the issue

await this.identitySDK.getWhitelistedRoot(userAddress)
if (!isWhitelisted) {
await this.fvRedirect()
// Use IdentitySDK's navigation method to eliminate code duplication

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

comments should not describe changes done for a commit.
it should explain current behavior, possibly edge-cases or why something has been done in a particular way.

Thats why my suggestion

Comment thread apps/demo-identity-app/src/App.tsx Outdated
setIsVerified(isWhitelisted ?? false)
} catch (error) {
console.error("Error checking whitelist:", error)
} catch {

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe the setIsWhitelisted should have been added in the catch but there is no reason to remove the error log

throw new Error(`Failed to submit transaction: ${error.message}`)
}
}

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unneccessary remove of comment @vortex-hue

functionName: "getWhitelistedRoot",
args: [account],
})
const root = await this.publicClient.readContract({

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Your commit statement that changed this reads: - Simplify error handling by removing redundant try-catch blocks

  1. would like to understand your train of thought why this seems redundant?
  2. The explanation why its not redundant is because we are providing both developer and user a meaningful error to handle or see without the front-end of the app breaking.
    If you dont catch here, and there is an rpc error, it will break frontend with a runtime error.

Developer: might want to direct users to a particular screen/flow, might show a particular error, depending on what error is returned by the rpc
User: wants to know whats happening, and wants to know why something is not working

revert changes that were applied based on 'redundant try-catch blocks' they are there for a reason

}
}

async navigateToFaceVerification(

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if not used, remove

Comment thread packages/citizen-sdk/src/utils/auth.ts Outdated
Comment on lines +149 to +158
if (!url.pathname.includes('/verify') && !url.pathname.includes('/callback')) {
url.pathname = url.pathname.endsWith('/')
? `${url.pathname}verify`
: `${url.pathname}/verify`;
}

if (additionalParams) {
Object.entries(additionalParams).forEach(([key, value]) => {
url.searchParams.set(key, value);
});

ghost Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think the original request has not been understood.
we are not going to enforce users to create a particular endpoint.
What we provide is helpers to the verified param, for them to handle UX/UI.

you get a queryParam 'verified' (true/false).
this shows if someone face-verification has been succesfull.
all we need is a helper that parses it.
in the original issue a reference has been shared where we do this in our old sdk: https://github.com/GoodDollar/GoodWeb3-Mono/blob/9846f16b16f2d8406407dd48231ea7c9e33e751e/packages/good-design/src/core/buttons/ClaimButton.tsx#L31

- Restore try-catch in getWhitelistedRoot with meaningful error message
- Re-throw original error in submitAndWait to preserve error type
- Simplify createVerificationCallbackUrl (remove /verify path enforcement)
- Add parseVerificationResult helper for verified query param parsing
- Restore console.error in App.tsx catch block
- Remove unused navigateToFaceVerification method
- Remove commit-style comment in fvRedirect
Comment thread apps/demo-identity-app/package.json Outdated
"zod": "^3.24.2"
},
"devDependencies": {
"@farcaster/miniapp-sdk": "^0.1.8",

ghost Aug 24, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also peer dep

Comment thread apps/demo-identity-app/vite.config.mts Outdated
output: {
globals: nodeBuiltinGlobals
}
external: ["@goodsdks/citizen-sdk", "viem"]

ghost Aug 24, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread packages/citizen-sdk/package.json Outdated
"wagmi": "*"
},
"dependencies": {
"@farcaster/miniapp-sdk": "^0.1.8",

ghost Aug 24, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dev?peer? @L03TJ3

const fvLink = await identitySDK.generateFVLink(
false,
window.location.href,
42220,

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why was this removed

}
}

/**

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why was this removed

Comment on lines +29 to +33
createVerificationCallbackUrl,
createFarcasterCallbackUniversalLink,
isInFarcasterMiniApp,
navigateToUrl
} from "../utils/auth"

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this here? this is the claim sdk.
the claim sdk can use the identity sdk

* Initialize the callback URL with proper Farcaster Universal Link support
* @param rdu - The redirect URL after claim
*/
private async initializeCallbackUrl(rdu?: string): Promise<void> {

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is duplicate. it should be in the identity sdk

return await waitForTransactionReceipt(this.publicClient, { hash })
} catch (error: any) {
console.error("submitAndWait Error:", error)
throw new Error(`Failed to submit transaction: ${error.message}`)

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why

}
}

/**

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

restore all docs

Comment thread packages/citizen-sdk/src/constants.ts Outdated
*
* The SDK will default to these values if no config is provided, which will likely fail in production.
*/
export const FarcasterAppConfigs: Record<string, { appId: string; appSlug: string }> = {

ghost Feb 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

apps dont have envs in farcaster

@sirpy

ghost commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

@vortex-hue This has been open for a very long time. I want to close it this month.
I suggest you take some time to address all comments, clean your code and provide the missing mobile demo

@vortex-hue

ghost commented Feb 12, 2026 •

Copy link
Copy Markdown
Author

Hi @sirpy, thanks for the review, I'd take some time during this week &/ weekend to resolve all issues then, and would also provide the demos.

Thanks for the patience.

Peter Benjamin Ani added 3 commits February 16, 2026 08:22
- Restore all original JSDoc, try-catch, and error handling patterns
- Add resolveCallbackUrl to IdentitySDK for Farcaster/URL consolidation
- Remove duplicate Farcaster logic from ClaimSDK (delegate to IdentitySDK)
- Remove FarcasterAppConfigs (apps don't have envs in Farcaster)
- Remove redundant isAddressWhitelisted from auth utils
- Move @farcaster/miniapp-sdk to peerDependencies
- Restore VerifyButton original props and chain ID
- Add navigateToFaceVerification() to IdentitySDK for Farcaster-aware FV navigation
- ClaimSDK.fvRedirect() delegates to IdentitySDK instead of using window.location.href
- Refactor FarcasterAppConfig: domain is now required, appId/appSlug optional
- Support domain-based Farcaster deep links for redirect-back flow
- Pass farcasterConfig through useIdentitySDK and useClaimSDK hooks
- VerifyButton uses navigateToFaceVerification() for correct navigation
@vortex-hue

ghost commented Feb 16, 2026 •

Copy link
Copy Markdown
Author

Hi @sirpy @L03TJ3 , here's the summary of everything done and abit of request from me.

So on Mobile (Farcaster Mini App): When a user taps "Verify Me" inside the miniapp, the SDK detects the Farcaster context and uses sdk.actions.openUrl to open the GoodDollar face verification page in the phone's browser. After verification, GoodDollar redirects to a Farcaster deep link (farcaster.xyz/~/mini-apps/launch?domain=...&verified=true), which the OS intercepts and reopens the Farcaster app — bringing the user back to the miniapp with the verified status.

On Web Browser: When a user clicks "Verify Me" on a regular web app, the SDK navigates directly to the GoodDollar face verification page via window.location.href. After verification, GoodDollar redirects back to the app's URL with ?verified=true, and the app reads the param to update the UI.

NB:

  • I've tested the full redirect flow and confirmed it works — the miniapp correctly opens the browser for FV and the deep link brings the user back to Farcaster. I usually click "I give up" and it redirects me back to farcaster.

  • The actual facial verification itself keeps failing during my demos, making it difficult to show the complete end-to-end flow.

  • If a staging/mock FV endpoint that bypasses the real liveness check could be provided for demo purposes, that would be very helpful.

sirpy
sirpy previously requested changes Feb 16, 2026

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. you should be able to use identity sdk with dev env, it checks liveness but shouldnt check for duplicates.
  2. in order to show it is working you need to create a demo mini app and then access this app via farcaster web and also via farcaster mobile. you can follow their developer guides how to test apps.

params[popupMode ? "cbu" : "rdu"] = callbackUrl
const isInFarcaster = await isInFarcasterMiniApp();

if (isInFarcaster && this.farcasterConfig) {

ghost Feb 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

identity sdk should not hold farcasterConfig
this whole logic should be extracted to a helper method

See this psuedo code
farcasterCallback = await identitysdk.generateFarcasterCallbac(farcasterconfig) identitysdk.navigateTofaceVerification(farcastercallback)

ghost Feb 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So requested changes @peterje-tr:

  1. farcaster handling should be done in their own class helpers.
  2. farcasterconfig is managed on the client-side, and passed down to relevant methods on a per-needed basis.
  3. navigateToFaceverification should be an optional helper, not enforced in flows

* @returns The resolved callback URL.
*/
async resolveCallbackUrl(
baseUrl: string,

ghost Feb 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

see my previous comment


export const useClaimSDK = (
env: contractEnv = "production",
farcasterConfig?: FarcasterAppConfig,

ghost Feb 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that shouldnt be here


export const useIdentitySDK = (
env: contractEnv = "production",
farcasterConfig?: FarcasterAppConfig,

ghost Feb 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should be removed

@sirpy
sirpy requested a review from L03TJ3 February 16, 2026 12:03
- Removed `farcasterConfig` from `IdentitySDK` options and class state
- Removed `farcasterConfig` param from `useIdentitySDK` and `useClaimSDK` hooks
- Reverted `generateFVLink` back to using `callbackUrl` directly without internal interception
- Removed `resolveCallbackUrl` helper method
- Reverted `ClaimSDK.fvRedirect()` to use `window.location.href`
- Added explicit `generateFarcasterCallback` method to `IdentitySDK`
- Updated `VerifyButton` to handle Farcaster context explicitly and generate callback URL locally
@L03TJ3

ghost commented Mar 31, 2026

Copy link
Copy Markdown
Collaborator

@vortex-hue Hey, its been pending to include demo-video. (as requested on TG)
there are also now merge conflicts to resolve

@vortex-hue

ghost commented Apr 1, 2026 via email

Copy link
Copy Markdown
Author

@L03TJ3
L03TJ3 requested a review from pheobeayo July 7, 2026 09:10

ghost left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

QA Report
Env: Microsoft Edge & Chrome (desktop) / Ubuntu + Warpcast mobile app (Android) /
Wallet: MetaMask / Network: Celo mainnet
Branch: add-farcaster-support-for-the-identity-flow (commit 59484bc or later)

Tests

Scenario Expected Actual ✅/❌
Desktop browser (Edge & Chrome) — outside Farcaster context isInFarcasterMiniApp() returns false; normal (non-miniapp) verification flow loads and completes Confirmed working in both browsers ✅
Farcaster mini app launch (Warpcast mobile, via ngrok tunnel + fc:miniapp embed) App loads and identity verification UI becomes visible/interactive Mini app launches with correct title ("GoodDollar Identity Demo") in header, but content area stays blank indefinitely (60+ sec, no change) ❌
Redirect handling (rdu/cbu) post-verification N/A — blocked by above Not reachable, could not test ❌ (blocked)

Bugs: 1 (Blocker — see separate comment below)
Verdict: Fail
Evidence: https://www.loom.com/share/a9e494d15c494806ab28e7e67142e813,
WhatsApp Image 2026-07-08 at 4 45 55 AM

@pheobeayo

ghost commented Jul 8, 2026 •

Copy link
Copy Markdown

QA Report Env: Microsoft Edge & Chrome (desktop) / Ubuntu + Warpcast mobile app (Android) / Wallet: MetaMask / Network: Celo mainnet Branch: add-farcaster-support-for-the-identity-flow (commit 59484bc or later)

Tests

Scenario Expected Actual ✅/❌
Desktop browser (Edge & Chrome) — outside Farcaster context isInFarcasterMiniApp() returns false; normal (non-miniapp) verification flow loads and completes Confirmed working in both browsers ✅
Farcaster mini app launch (Warpcast mobile, via ngrok tunnel + fc:miniapp embed) App loads and identity verification UI becomes visible/interactive Mini app launches with correct title ("GoodDollar Identity Demo") in header, but content area stays blank indefinitely (60+ sec, no change) ❌
Redirect handling (rdu/cbu) post-verification N/A — blocked by above Not reachable, could not test ❌ (blocked)
Bugs: 1 (Blocker — see separate comment below) Verdict: Fail Evidence: https://www.loom.com/share/a9e494d15c494806ab28e7e67142e813, WhatsApp Image 2026-07-08 at 4 45 55 AM

Severity: Blocker
Summary: Farcaster mini app never becomes interactive — stuck on a blank screen indefinitely because sdk.actions.ready() is never called

Environment: Android / Warpcast mobile app / Farcaster Mini App preview tool

Steps to reproduce:

  1. Serve apps/demo-identity-app publicly (tested via ngrok tunnel) with an fc:miniapp embed meta tag added
  2. Open farcaster.xyz/~/developers/mini-apps/preview, paste the URL, tap Preview
  3. Tap "Verify Identity" on the embed card to launch the mini app

Expected: App loads and the identity verification UI becomes visible/interactive within a few seconds

Actual: Mini app opens with the correct title ("GoodDollar Identity Demo") in the header, but the content area stays blank for 60+ seconds with no change

Evidence: [attach blank launch screen screenshot]
Related to this PR? Yes — sdk.actions.ready(), required by the Farcaster Mini App spec to dismiss the host's loading screen, is never called anywhere in the codebase. Checked packages/citizen-sdk/src/utils/auth.ts and apps/demo-identity-app — sdk.actions.openUrl() and sdk.isInMiniApp() are called, but ready() is not. This blocks confirming redirect (rdu/cbu) handling entirely, since the app never becomes reachable inside an actual Farcaster client.

@GoodDollar/goodbuilders-maintainers, @L03TJ3 QA complete on this PR; filed a Blocker bug above. Happy to re-test once addressed.

@L03TJ3
L03TJ3 changed the base branch from main to finalize-farcaster-support July 14, 2026 11:52
@L03TJ3
L03TJ3 dismissed sirpy’s stale review July 14, 2026 11:55

Stale or already resolved

ghost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

not approved but preparing for merge in local pull-request for copilot fixes

@L03TJ3
L03TJ3 merged commit e5ddb62 into GoodDollar:finalize-farcaster-support Jul 14, 2026
@pheobeayo pheobeayo mentioned this pull request Jul 23, 2026
2 tasks
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.

Add Farcaster support for the identity flow

4 participants