Skip to content

Conversation

@vortex-hue
Copy link

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

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

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
Copy link

korbit-ai bot commented Aug 14, 2025

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.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

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.

@vortex-hue
Copy link
Author

vortex-hue commented Aug 14, 2025

review changes by sorcery-ai implemented

Copy link
Collaborator

@L03TJ3 L03TJ3 left a comment

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?

@vortex-hue
Copy link
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
Author

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

Copy link
Contributor

@sirpy sirpy left a comment

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

"format": "prettier --write ."
},
"dependencies": {
"@farcaster/miniapp-sdk": "^0.1.8",
Copy link
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
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 on lines 31 to 35
build: {
rollupOptions: {
external: ["@goodsdks/citizen-sdk"]
}
}
Copy link
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
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
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
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

@sirpy
Copy link
Contributor

sirpy commented Aug 17, 2025

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

@vortex-hue
Copy link
Author

vortex-hue commented Aug 23, 2025

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
Copy link
Contributor

sirpy commented Aug 24, 2025

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
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
Author

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

@vortex-hue
Copy link
Author

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

@sirpy
Copy link
Contributor

sirpy commented Sep 2, 2025

@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
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 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
Collaborator

Choose a reason for hiding this comment

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

Comment on lines 72 to 74
if (typeof window === "undefined") {
throw new Error("URL opening is only supported in browser environments.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

* @param additionalParams - Additional parameters to include
* @returns A universal link compatible URL
*/
export function createUniversalLinkCallback(
Copy link
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?

* @param additionalParams - Additional parameters to include
* @returns A universal link compatible URL
*/
export function createUniversalLinkCallback(
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vercel
Copy link

vercel bot commented Sep 2, 2025

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
@vortex-hue
Copy link
Author

hi @L03TJ3 , I've reverted them, you can check it now.

@vortex-hue vortex-hue requested a review from L03TJ3 September 13, 2025 14:00
)
const { sdk: identitySDK } = useIdentitySDK("development")

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. clean this up. if we want to show someone how the sdk could be implemented, this is not easy to follow
  2. perhaps consider making it part of the VerifyButton.tsx ?
  3. include a README with examples in citizen-sdk for how it works/should be implemented

Copy link
Author

Choose a reason for hiding this comment

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

Hi Lewis, I don't fully understand this, mind explaining deeper please?

Copy link
Author

Choose a reason for hiding this comment

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

do you mean I should revert the file to how it was initially? and remove every code I added relating to the forecaster support?

@vortex-hue vortex-hue force-pushed the add-farcaster-support-for-the-identity-flow branch from bc861ab to 3bd36b1 Compare October 4, 2025 16:53
@vortex-hue vortex-hue requested a review from L03TJ3 October 4, 2025 16:59
@vortex-hue vortex-hue force-pushed the add-farcaster-support-for-the-identity-flow branch from 2654e1c to 4060809 Compare October 9, 2025 11:22

interface VerifyButtonProps {
onVerificationSuccess: () => void
// No props needed - verification success is handled by URL callback detection
Copy link
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 @vortex-hue

Copy link
Author

Choose a reason for hiding this comment

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

done

Copy link
Collaborator

@L03TJ3 L03TJ3 left a comment

Choose a reason for hiding this comment

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

the project does not build:

│ src/sdks/viem-claim-sdk.ts:20:2: ERROR: No matching export in "src/constants.ts" for import "contractAddresses"
│ src/sdks/viem-claim-sdk.ts:22:26: ERROR: No matching export in "src/constants.ts" for import "getGasPrice"
│ src/utils/auth.ts:2:50: ERROR: No matching export in "src/constants.ts" for import "contractAddresses"

import { waitForTransactionReceipt } from "viem/actions"

import { IdentitySDK } from "./viem-identity-sdk"
import { createUniversalLinkCallback } from "../utils/auth"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not seem to be used, why is it imported?

Resolved conflicts in:
- packages/citizen-sdk/src/sdks/viem-claim-sdk.ts
- packages/citizen-sdk/src/sdks/viem-identity-sdk.ts

Merged changes:
- Kept Farcaster miniapp support (Universal Links, callback URLs)
- Integrated new chain management system with fallbacks
- Added RPC fallback mechanism
- Refactored faucet trigger utility
- Updated to use identitySDK.navigateToFaceVerification() instead of fvRedirect()
After merging upstream/main, the contract address structure changed from
contractAddresses to chainConfigs. Updated isAddressWhitelisted function
to use the new chainConfigs structure.
@vortex-hue vortex-hue requested a review from L03TJ3 November 10, 2025 22:12
@L03TJ3 L03TJ3 linked an issue Nov 21, 2025 that may be closed by this pull request
6 tasks
- Remove excessive console.log statements from auth utilities and SDKs
- Simplify error handling by removing redundant try-catch blocks
- Remove verbose JSDoc comments that state the obvious
- Consolidate duplicate navigation functions
- Improve code readability and maintainability
- Follow KISS, DRY, and clarity over cleverness principles
- Remove unnecessary console.log/console.error statements
- Remove unused onVerificationSuccess prop from VerifyButton
- Simplify error handling in demo components
- Rename callBackExample to txConfirm for clarity
- Follow DRY and KISS principles
Copy link
Collaborator

@L03TJ3 L03TJ3 left a comment

Choose a reason for hiding this comment

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

Summary of the comments:
A proposed solution in a PR to resolve an issue should tackle only what is relevant to your solution and should not introduced extra refactors or changes without an explicit reason

} catch (error) {
console.error("Verification failed:", error)
// Handle error (e.g., show toast)
} catch {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This refactor is both not needed or in any way related to your fixes for farcaster + its not true.
generateFVLink throws an error and is not handled there

},
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

If these are just placeholders and not working this should be clarified in a readme and should be configurable options for developers to pass down
(we are not hosting a farcaster app, developers integrating the identitysdk/claim-sdk might)

}
}

export async function handleVerificationResponse(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not being used anywhere, whats the purpose?
if not needed, remove

}
}

export function handleVerificationResponseSync(url?: string): {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not being used anywyere, if not needed, remove

return universalLink;
}

export async function createVerificationCallbackUrl(
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this supposed to be handling?

Copy link
Author

Choose a reason for hiding this comment

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

It validates the URL and enforces a routing convention (appending /verify if missing) to ensure the callback hits a route handled by the SDK/app

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

Choose a reason for hiding this comment

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

Why got the comments removed?

Copy link
Author

Choose a reason for hiding this comment

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

was cleaning up the codebase

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

Choose a reason for hiding this comment

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

What are these params used for, and why hardcode 'verified': true?

if (!isWhitelisted) {
await this.fvRedirect()
// Use IdentitySDK's navigation method to eliminate code duplication
await this.identitySDK.navigateToFaceVerification(
Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. what duplication is being handled here exactly?
  2. if you choose to do a refactor, always make sure functionality does not change along the way

You introduce: hardcoded chain-id, different behavior of reloading page etc.
Its not necessarily a bad idea to move handling of face-verification to the identity-sdk.

but it should always be trippled checked against exisiting flows.
how would this affect dapps that already have this flow?

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. chainid should not be hardcoded to celo
  2. Flow should stay the same as it was, changes needed for your flow should only affect your intended flow

And include a demo that this change works as expected, as I am not sure this is the way to handle pop-up window (fallbackToNewTab? should it not just close the pop-up window?)

return new IdentitySDK({ account, ...props })
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why removing comment?

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

Choose a reason for hiding this comment

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

You are removing a valid catch error flow that could be used to give feedback to a user

- Restore original fvRedirect behavior in ClaimSDK (window.location.href)
- Use fvDefaultChain instead of hardcoded chainId
- Restore error handling in submitAndWait
- Restore removed JSDoc comments
- Remove unused handleVerificationResponse functions
- Fix VerifyButton error handling
- Add FarcasterAppConfigs documentation to README
- Add JSDoc to createVerificationCallbackUrl
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

3 participants