Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useMiniKit } from "@coinbase/onchainkit/minikit";
import { getUserContext } from "@/lib/user-context";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { usePrivyAuth } from "@/hooks/usePrivyAuth";
import { useWalletAuth } from "@/hooks/useWalletAuth";

export default function ProfilePage() {
const { context } = useMiniKit();
const user = getUserContext(context);
const { talentId } = usePrivyAuth({});
const { talentId } = useWalletAuth({});
const router = useRouter();

useEffect(() => {
Expand Down
15 changes: 3 additions & 12 deletions app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import { type ReactNode } from "react";
import { base } from "wagmi/chains";
import { MiniKitProvider } from "@coinbase/onchainkit/minikit";
import { OnchainKitProvider } from "@coinbase/onchainkit";
import { getMiniKitConfig } from "@/lib/app-metadata";
import { PostHogProvider } from "@/components/PostHogProvider";
import { PrivyProvider } from "@privy-io/react-auth";

export function Providers(props: { children: ReactNode }) {
const miniKitConfig = getMiniKitConfig();
Expand All @@ -16,18 +16,9 @@ export function Providers(props: { children: ReactNode }) {
console.error("NEXT_PUBLIC_ONCHAINKIT_API_KEY is not defined");
}

const privyAppId = process.env.NEXT_PUBLIC_PRIVY_APP_ID!;
const privyClientId = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID!;

if (!privyAppId || !privyClientId) {
console.error(
"NEXT_PUBLIC_PRIVY_APP_ID or NEXT_PUBLIC_PRIVY_CLIENT_ID is not defined",
);
}

return (
<PostHogProvider>
<PrivyProvider appId={privyAppId} clientId={privyClientId}>
<OnchainKitProvider apiKey={apiKey || ""} chain={base}>
<MiniKitProvider
apiKey={apiKey || ""}
chain={base}
Expand All @@ -42,7 +33,7 @@ export function Providers(props: { children: ReactNode }) {
>
{props.children}
</MiniKitProvider>
</PrivyProvider>
</OnchainKitProvider>
</PostHogProvider>
);
}
4 changes: 2 additions & 2 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ import {
Share,
} from "lucide-react";
import { openExternalUrl } from "@/lib/utils";
import { usePrivyAuth } from "@/hooks/usePrivyAuth";
import { useShareCreatorScore } from "@/hooks/useShareCreatorScore";
import { ShareCreatorScoreModal } from "@/components/modals/ShareCreatorScoreModal";
import { useWalletAuth } from "@/hooks/useWalletAuth";
import { usePostHog } from "posthog-js/react";
import { Typography } from "@/components/ui/typography";

export default function SettingsPage() {
const router = useRouter();
const { handleLogout, authenticated } = usePrivyAuth({});
const { talentUuid, loading: loadingUserResolution } = useFidToTalentUuid();
const { handleLogout, authenticated } = useWalletAuth({});
const posthog = usePostHog();
const { isOpen, onOpenChange, openForTesting } = useShareCreatorScore();

Expand Down
46 changes: 17 additions & 29 deletions components/modals/FarcasterAccessModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "@/components/ui/drawer";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
import { useLogin, usePrivy } from "@privy-io/react-auth";
import { ConnectWallet } from "@coinbase/onchainkit/wallet";
import { useRouter } from "next/navigation";

function useMediaQuery(query: string) {
Expand Down Expand Up @@ -50,17 +50,13 @@ export function FarcasterAccessModal({
const isDesktop = useMediaQuery("(min-width: 768px)");
const drawerContentRef = React.useRef<HTMLDivElement>(null);
const previouslyFocusedElement = React.useRef<HTMLElement | null>(null);
const { ready } = usePrivy();

const router = useRouter();
const { login } = useLogin({
onComplete: () => {
if (open) {
onOpenChange(false);
router.push(redirectPath);
}
},
});
const handleConnected = () => {
if (open) {
onOpenChange(false);
router.push(redirectPath);
}
};

const handleFarcasterClick = () => {
window.open(
Expand Down Expand Up @@ -101,8 +97,8 @@ export function FarcasterAccessModal({
<DialogHeader>
<DialogTitle>Check your Creator Score</DialogTitle>
<DialogDescription>
To view your score and access all features, please login with
Privy or use our Mini App.
To view your score and access all features, please connect your
wallet or use our Mini App.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2">
Expand All @@ -115,13 +111,9 @@ export function FarcasterAccessModal({
Open in Farcaster
</Button>

<Button
onClick={() => login({ walletChainType: "ethereum-only" })}
className="w-full"
disabled={!ready}
>
Login with Privy
</Button>
<div className="w-full">
<ConnectWallet onConnect={handleConnected} />
</div>
</div>
</DialogContent>
</Dialog>
Expand All @@ -134,8 +126,8 @@ export function FarcasterAccessModal({
<DrawerHeader>
<DrawerTitle>Check your Creator Score</DrawerTitle>
<DrawerDescription>
To view your score and access all features, please login with Privy
or use our Mini App.
To view your score and access all features, please connect your
wallet or use our Mini App.
</DrawerDescription>
</DrawerHeader>
<div
Expand All @@ -152,13 +144,9 @@ export function FarcasterAccessModal({
<ExternalLink className="w-4 h-4 mr-2" />
Open in Farcaster
</Button>
<Button
onClick={() => login({ walletChainType: "ethereum-only" })}
className="w-full"
disabled={!ready}
>
Login with Privy
</Button>
<div className="w-full">
<ConnectWallet onConnect={handleConnected} />
</div>
</div>
</div>
</DrawerContent>
Expand Down
4 changes: 2 additions & 2 deletions components/navigation/BottomNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { usePathname } from "next/navigation";
import { Icon } from "@/components/ui/icon";
import { useUserNavigation } from "@/hooks/useUserNavigation";
import { FarcasterAccessModal } from "@/components/modals/FarcasterAccessModal";
import { usePrivyAuth } from "@/hooks/usePrivyAuth";
import { useWalletAuth } from "@/hooks/useWalletAuth";

export function BottomNav() {
const [mounted, setMounted] = React.useState(false);
Expand All @@ -15,7 +15,7 @@ export function BottomNav() {
const [showModal, setShowModal] = React.useState(false);
const [clickedIcon, setClickedIcon] = React.useState<string | null>(null);
const [redirectPath, setRedirectPath] = React.useState<string>("/profile");
const { talentId } = usePrivyAuth({});
const { talentId } = useWalletAuth({});

React.useEffect(() => {
setMounted(true);
Expand Down
4 changes: 2 additions & 2 deletions components/navigation/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Icon } from "@/components/ui/icon";
import { useUserNavigation } from "@/hooks/useUserNavigation";
import { useBackButton } from "@/hooks/useBackButton";
import { FarcasterAccessModal } from "@/components/modals/FarcasterAccessModal";
import { usePrivyAuth } from "@/hooks/usePrivyAuth";
import { useWalletAuth } from "@/hooks/useWalletAuth";

export function Header() {
const pathname = usePathname();
Expand All @@ -19,7 +19,7 @@ export function Header() {
const [showModal, setShowModal] = React.useState(false);
const [clickedIcon, setClickedIcon] = React.useState<string | null>(null);
const [redirectPath, setRedirectPath] = React.useState<string>("/profile");
const { talentId } = usePrivyAuth({});
const { talentId } = useWalletAuth({});

const handleTitleClick = () => {
router.push("/leaderboard");
Expand Down
137 changes: 137 additions & 0 deletions docs/planning/privy-to-onchainkit-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
### Goal

Replace Privy authentication with OnchainKit provider and wallet flows, keeping MiniKit support for Farcaster Mini App context.

Reference: [Next.js Installation · OnchainKit](https://docs.base.org/onchainkit/installation/nextjs)

### Summary of changes

- Replace `PrivyProvider` with `OnchainKitProvider` in `app/providers.tsx`.
- Introduce a new auth hook powered by Wagmi/OnchainKit to replace `usePrivyAuth`.
- Swap Privy login/logout usage in UI with OnchainKit Wallet connect/disconnect.
- Update copy: “Login with Privy” → “Connect Wallet”.
- Remove Privy dependencies and env vars.

### Status

- Completed:
- Provider swap: `OnchainKitProvider` added; `PrivyProvider` removed in `app/providers.tsx` (kept `MiniKitProvider`).
- New auth hook: `hooks/useWalletAuth.ts` created (ports talent-user caching/UUID resolution).
- Call sites updated to use `useWalletAuth`:
- `app/profile/page.tsx`
- `components/navigation/Header.tsx`
- `app/settings/page.tsx`
- `hooks/useUserResolution.ts`
- `components/navigation/BottomNav.tsx`
- `FarcasterAccessModal.tsx` now uses `ConnectWallet` (with `onConnect`) and copy updated to “connect your wallet”.
- Privy removed from `package.json` and `hooks/usePrivyAuth.ts` deleted.
- Build passes after changes.
- Pending:
- Remove `NEXT_PUBLIC_PRIVY_APP_ID` and `NEXT_PUBLIC_PRIVY_CLIENT_ID` from `.env` and deployment configs.
- Optional: add `components/common/ConnectWalletButton.tsx` if we want a styled wrapper for multiple places.
- Optional: feature flag `AUTH_PROVIDER_ONCHAINKIT` not added (skipped for now).
- QA passes for manual scenarios below (smoke tested build only).

### Prerequisites

1. Ensure dependency present (already in repo): `@coinbase/onchainkit`
2. Env var: add `NEXT_PUBLIC_ONCHAINKIT_API_KEY` in `.env`
3. Styles are already included in `app/layout.tsx`: `import '@coinbase/onchainkit/styles.css'`

### Step-by-step plan

1. Add OnchainKit provider wrapper
- File: `app/providers.tsx`
- Replace `PrivyProvider` with `OnchainKitProvider` wrapping the app. Keep `MiniKitProvider` so `useMiniKit` continues to work.
- Example structure (adjust only the provider layer):
```tsx
// inside <PostHogProvider>
<OnchainKitProvider
apiKey={process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY}
chain={base}
>
<MiniKitProvider
apiKey={process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY}
chain={base}
config={
{
/* existing config */
}
}
>
{children}
</MiniKitProvider>
</OnchainKitProvider>
```
- Remove Privy env checks and `<PrivyProvider>` usage.
- Status: Done.

2. Create a replacement auth hook
- New file: `hooks/useWalletAuth.ts`
- Responsibilities:
- Source connection status via Wagmi (`useAccount`, `useDisconnect`).
- Maintain existing talent user resolution and caching behavior (port logic from `hooks/usePrivyAuth.ts`: `fetchTalentUser`, `globalTalentUserCache`, `globalTalentUserId`, debounce, localStorage sync).
- Return signature analogous to `usePrivyAuth`:
- `ready` (wagmi status is available), `authenticated` (connected), `walletAddress`, `talentId`, `handleLogin`, `handleLogout`.
- Implement `handleLogin` as a no-op trigger that consumers replace with a Connect Wallet component, and `handleLogout` using `disconnect()`.
- Status: Done.

3. Replace usages of `usePrivyAuth`
- Files to update:
- `app/profile/page.tsx` – import and use `useWalletAuth` for `talentId`.
- `components/navigation/Header.tsx` – import and use `useWalletAuth` for gating nav.
- `app/settings/page.tsx` – import and use `useWalletAuth` for `authenticated` and `handleLogout`.
- `hooks/useUserResolution.ts` – import and use `useWalletAuth` for `talentId`.
- `components/navigation/BottomNav.tsx` – import and use `useWalletAuth` for `talentId`.
- Status: Done.

4. Update `FarcasterAccessModal`
- File: `components/modals/FarcasterAccessModal.tsx`
- Remove `usePrivy` and `useLogin` usage.
- Replace “Login with Privy” button with OnchainKit Wallet connect UI:
- Inline component `ConnectWallet` from OnchainKit Wallet package.
- Update copy to “Connect Wallet”.
- On successful connection, close modal and push to `redirectPath` (use `ConnectWallet`’s `onConnect`).
- Status: Done.

5. Add a reusable Connect Wallet entry point
- If desired, create `components/common/ConnectWalletButton.tsx` that renders OnchainKit’s connect component with consistent styling.
- Use it anywhere a connect control is needed.
- Status: Optional, not implemented.

6. Remove Privy provider and dependencies
- File: `app/providers.tsx` – delete `PrivyProvider` import and usage.
- `package.json` – remove `@privy-io/react-auth` and `@privy-io/server-auth`.
- `.env` – remove `NEXT_PUBLIC_PRIVY_APP_ID`, `NEXT_PUBLIC_PRIVY_CLIENT_ID`.
- Status: Code and packages done; env cleanup pending.

7. Adjust copy and UX
- Search and replace UI text: “Login with Privy” → “Connect Wallet”.
- Ensure any docs/tooltips reference OnchainKit Wallet instead of Privy.
- Status: Done where applicable.

8. QA checklist
- Build boots without Privy provider and packages (done).
- From a cold start with no localStorage, connect a wallet via OnchainKit:
- Talent UUID correctly resolved and cached.
- `app/profile/page.tsx` redirects as before using `talentId` or MiniKit user.
- Disconnect (logout) from Settings works and redirects to `/leaderboard`.
- Mini App flows (`useMiniKit`) remain unaffected.
- No references to Privy left in codebase or UI.
- Status: Pending manual verification in runtime.

9. Optional: staged rollout
- Introduce a temporary feature flag `AUTH_PROVIDER_ONCHAINKIT` to toggle new auth in non-prod if desired. Keep flag usage centralized (e.g., in `app/providers.tsx` and hook export), then remove after rollout.
- Status: Skipped.

### Implementation notes

- Keep provider order: `PostHogProvider` → `OnchainKitProvider` → `MiniKitProvider` → children.
- OnchainKit provider under the hood creates Wagmi and QueryClient providers; do not add additional Wagmi providers unless customizing.
- Ensure `@coinbase/onchainkit/styles.css` remains imported at app root.

### Rollback plan

- Revert `app/providers.tsx` to Privy provider version.
- Restore `hooks/usePrivyAuth.ts` imports in updated files.
- Reinstall Privy packages and re-add env vars.
4 changes: 2 additions & 2 deletions hooks/useResolvedTalentProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useEffect, useMemo, useState } from "react";
import { useMiniKit } from "@coinbase/onchainkit/minikit";
import { getUserContext } from "@/lib/user-context";
import { usePrivyAuth } from "@/hooks/usePrivyAuth";
import { useWalletAuth } from "@/hooks/useWalletAuth";
import { resolveFidToTalentUuid } from "@/lib/user-resolver";
import type { UnifiedUserProfile } from "@/app/services/types";

Expand All @@ -15,7 +15,7 @@ type HookReturn = Omit<UnifiedUserProfile, "error"> & {
export function useResolvedTalentProfile(): HookReturn {
const { context } = useMiniKit();
const farcasterUser = getUserContext(context);
const { talentId } = usePrivyAuth({});
const { talentId } = useWalletAuth({});

const [talentUuid, setTalentUuid] = useState<string | null>(null);
const [data, setData] = useState<UnifiedUserProfile | null>(null);
Expand Down
4 changes: 2 additions & 2 deletions hooks/useUserResolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { useState, useEffect } from "react";
import { useMiniKit } from "@coinbase/onchainkit/minikit";
import { getUserContext } from "@/lib/user-context";
import { resolveFidToTalentUuid } from "@/lib/user-resolver";
import { usePrivyAuth } from "./usePrivyAuth";
import { useWalletAuth } from "./useWalletAuth";

// Session-level cache for user resolution
const userResolutionCache = new Map<number, string | null>();

export function useFidToTalentUuid() {
const { context } = useMiniKit();
const user = getUserContext(context);
const { talentId } = usePrivyAuth({});
const { talentId } = useWalletAuth({});
const [talentUuid, setTalentUuid] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand Down
Loading