Skip to content

feat: add Privy v3 Next.js App Router example#63

Open
spiritclawd wants to merge 5 commits intokeep-starknet-strange:mainfrom
spiritclawd:feat/privy-nextjs-example
Open

feat: add Privy v3 Next.js App Router example#63
spiritclawd wants to merge 5 commits intokeep-starknet-strange:mainfrom
spiritclawd:feat/privy-nextjs-example

Conversation

@spiritclawd
Copy link

@spiritclawd spiritclawd commented Mar 7, 2026

Closes #56

This PR adds a complete example demonstrating how to integrate Starkzap SDK with Privy for embedded Starknet wallets in a Next.js App Router application.

Features

  • 🔐 Social Login - Google, Twitter, Discord, Telegram, Email
  • 💳 Embedded Wallets - Privy creates and manages Starknet wallets
  • Server-Side Signing - Secure transaction signing via Next.js API routes
  • 🔄 Account Abstraction - Argent and OpenZeppelin account presets
  • 📱 Modern UI - Next.js App Router with Tailwind CSS

Files Added

File Purpose
app/api/wallet/starknet/route.ts Create/get embedded Starknet wallets
app/api/wallet/sign/route.ts Sign transaction hashes with Privy
components/PrivyProvider.tsx Privy React provider config
components/StarknetWallet.tsx Main wallet component with starkzap
lib/starkzap.ts Starkzap SDK configuration
README.md Comprehensive setup and usage documentation

Testing

  1. Copy .env.example to .env.local
  2. Add Privy credentials from dashboard.privy.io
  3. Run npm install && npm run dev
  4. Connect with social login and interact with Starknet!

Summary by CodeRabbit

  • New Features

    • Added a complete Next.js Privy + Starknet example: env example, README, .gitignore, project config (Next/Tailwind/PostCSS/TS), global styles, layout, pages (home, 404, error), demo UI, provider, Starknet wallet component, SDK helper, and backend API routes for wallet creation and signing.
  • Refactor

    • Read-only token and staking methods now accept either a wallet object or a raw address string (AddressLike).

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Number must be less than or equal to 900000 at "reviews.tools.github-checks.timeout_ms"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds a complete Next.js App Router example integrating Privy v3 with Starkzap for Starknet, plus a new AddressLike type and related refactors: example project scaffold, client components/providers, App Router API routes for wallet create/fetch/sign, global styles, SDK wrapper, and updated read-only library APIs to accept addresses or wallet objects.

Changes

Cohort / File(s) Summary
Project config & metadata
examples/privy-nextjs/package.json, examples/privy-nextjs/next.config.js, examples/privy-nextjs/tsconfig.json, examples/privy-nextjs/postcss.config.js, examples/privy-nextjs/tailwind.config.js, examples/privy-nextjs/.gitignore, examples/privy-nextjs/.env.example
Adds example project scaffold: package manifest, Next.js/TS/PostCSS/Tailwind configs, .gitignore, and .env.example with Privy/RPC placeholders and transpilePackages for starkzap.
Documentation
examples/privy-nextjs/README.md
New comprehensive README describing setup, architecture, Privy↔Starkzap integration, usage examples, and troubleshooting.
Server API routes
examples/privy-nextjs/app/api/wallet/sign/route.ts, examples/privy-nextjs/app/api/wallet/starknet/route.ts
New App Router endpoints: Bearer-authenticated Privy token verification, embedded Starknet wallet create/fetch, and server-side raw signing via Privy server SDK; lazy Privy client init and in-memory wallet store.
App shell & pages
examples/privy-nextjs/app/layout.tsx, examples/privy-nextjs/app/page.tsx, examples/privy-nextjs/app/error.tsx, examples/privy-nextjs/app/not-found.tsx, examples/privy-nextjs/app/globals.css
Adds root layout, index page, error/404 pages, global Tailwind CSS with Privy modal theming, utilities, metadata and force-dynamic directives.
Client components
examples/privy-nextjs/components/PrivyProvider.tsx, examples/privy-nextjs/components/StarknetWallet.tsx
Adds PrivyProvider wrapper and StarknetWallet component implementing auth flow, backend provisioning, OnboardStrategy.Privy integration, signing proxy usage, deployment/testing flows, and UI states.
SDK wrapper
examples/privy-nextjs/lib/starkzap.ts
Adds singleton StarkZap SDK configuration for Sepolia with exported presets and accessor for shared consumption.
Types & library API changes
src/types/address-like.ts, src/types/index.ts, src/erc20/erc20.ts, src/staking/staking.ts
Introduces AddressLike type and getAddress; refactors read-only APIs (erc20.balanceOf, staking.isMember/getPosition) to accept AddressLike and resolve addresses internally — public signatures changed.
Build configs
examples/privy-nextjs/next.config.js, examples/privy-nextjs/tailwind.config.js, examples/privy-nextjs/postcss.config.js
New Next/Tailwind/PostCSS config files to support example build, theme colors, and transpilation of starkzap.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as User (Browser)
    participant Client as StarknetWallet (React)
    participant PrivyUI as Privy (Client)
    participant Backend as Next.js API
    participant PrivyServer as Privy Server SDK
    participant Starkzap as Starkzap SDK

    User->>Client: Click "Connect Wallet"
    Client->>PrivyUI: Start login flow
    PrivyUI->>User: Show auth UI
    User->>PrivyUI: Authenticate
    PrivyUI-->>Client: Return access token

    Client->>Backend: POST /api/wallet/starknet (Bearer token)
    Backend->>PrivyServer: Verify token & get user
    PrivyServer-->>Backend: user info
    Backend->>PrivyServer: Create/lookup embedded wallet
    PrivyServer-->>Backend: walletId, publicKey, address
    Backend-->>Client: Wallet data (isNew / wallet info)

    Client->>Starkzap: Build Onboard with resolve (uses backend sign)
    Starkzap->>Backend: POST /api/wallet/sign (walletId, hash)
    Backend->>PrivyServer: walletApi.rpc (starknet_signMessage)
    PrivyServer-->>Backend: signature
    Backend-->>Starkzap: signature
    Starkzap->>Starkzap: Submit transaction
    Starkzap-->>Client: Transaction status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibbled docs and hopped through code,

Privy keys and Starknet road.
Wallets wake with a joyful spring,
Signatures flutter on tiny wing.
A rabbit cheers — the demo's ready to load!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive The PR includes two sets of changes: (1) example files in examples/privy-nextjs/ that are scope-appropriate, and (2) SDK changes to src/erc20/erc20.ts and src/staking/staking.ts that modify public API signatures from WalletInterface to AddressLike, which are supporting improvements for address flexibility but extend beyond the stated PR objective of adding an example. Clarify whether SDK API changes (WalletInterface→AddressLike in ERC20/Staking) are intentionally bundled with the example PR or should be separated into a dedicated refactoring PR for scope clarity.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Privy v3 Next.js App Router example' is concise and clearly summarizes the main addition—a complete Privy v3 integration example for Next.js App Router, which aligns with the primary objective from issue #56.
Linked Issues check ✅ Passed All requirements from issue #56 are met: the PR demonstrates obtaining walletId from Privy embedded Starknet wallets, implements rawSign using PrivyClient.walletApi.rpc with starknet:SN_SEPOLIA caip2, bridges Privy auth token from client to server, and provides a complete Next.js App Router example with API routes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 2

🧹 Nitpick comments (12)
examples/privy-nextjs/tsconfig.json (1)

2-22: Consider enabling additional strict TypeScript settings per codebase guidelines.

The configuration is solid for a Next.js App Router project, but the codebase guidelines specify two additional strict settings that should be enabled: noUncheckedIndexedAccess and exactOptionalPropertyTypes.

♻️ Proposed fix to add stricter type checking
   "compilerOptions": {
     "lib": ["dom", "dom.iterable", "esnext"],
     "allowJs": true,
     "skipLibCheck": true,
     "strict": true,
+    "noUncheckedIndexedAccess": true,
+    "exactOptionalPropertyTypes": true,
     "noEmit": true,

Based on learnings: "Use strict TypeScript compiler settings: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes enabled."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/tsconfig.json` around lines 2 - 22, The tsconfig.json
compilerOptions is missing two stricter flags required by our guidelines; update
the compilerOptions (in the same object where "strict" is set) to add
"noUncheckedIndexedAccess": true and "exactOptionalPropertyTypes": true so
TypeScript enforces safer indexed access and exact optional property semantics
across the codebase.
examples/privy-nextjs/app/api/wallet/starknet/route.ts (3)

4-5: Use server-only environment variable naming.

Same issue as in sign/route.ts—use a non-NEXT_PUBLIC_ prefixed variable for server-side code.

♻️ Proposed fix
-const PRIVY_APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID;
+const PRIVY_APP_ID = process.env.PRIVY_APP_ID;
 const PRIVY_APP_SECRET = process.env.PRIVY_APP_SECRET;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/app/api/wallet/starknet/route.ts` around lines 4 - 5,
The constant PRIVY_APP_ID is reading a client-exposed env var
(NEXT_PUBLIC_PRIVY_APP_ID) in server-side code; change it to read a server-only
variable instead (e.g., process.env.PRIVY_APP_ID) and ensure PRIVY_APP_SECRET
remains server-only, then update any usages of PRIVY_APP_ID in this file
(route.ts) to match the new variable name and adjust your environment
configuration (.env) to define PRIVY_APP_ID rather than
NEXT_PUBLIC_PRIVY_APP_ID.

23-29: Document the serverless limitation more prominently.

The in-memory walletStore will be cleared on serverless cold starts (Vercel, AWS Lambda, etc.). While commented, users may miss this critical limitation. The StarknetWallet.tsx component only calls POST (not GET first), which could create duplicate wallets after cold starts.

Consider adding a startup log warning or throwing an error in production builds without a database:

// Add near the walletStore declaration
if (process.env.NODE_ENV === 'production') {
  console.warn(
    '⚠️ WARNING: Using in-memory wallet storage. ' +
    'Wallets will be lost on server restart. ' +
    'Configure a persistent database for production.'
  );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/app/api/wallet/starknet/route.ts` around lines 23 - 29,
The in-memory walletStore map is unsafe for serverless cold starts and can cause
duplicate wallets (StarknetWallet.tsx posts without GET); modify the module that
declares walletStore to detect production and either log a prominent warning or
throw to prevent deployment without a persistent DB: check process.env.NODE_ENV
=== 'production' near the walletStore declaration and emit a clear console.warn
(including guidance to use Redis/Postgres/Mongo) or throw an Error to fail
startup in prod builds; reference the walletStore symbol and the
StarknetWallet.tsx behavior in the message so callers know to add a persistent
store.

7-21: Consider extracting shared Privy client initialization.

The getPrivyClient() function and lazy initialization pattern is duplicated in app/api/wallet/sign/route.ts. Consider extracting to a shared module like @/lib/privy.ts.

♻️ Example shared module

Create lib/privy.ts:

import { PrivyClient } from '@privy-io/node';

let privyClient: PrivyClient | null = null;

export function getPrivyClient(): PrivyClient {
  if (!privyClient) {
    const appId = process.env.PRIVY_APP_ID;
    const appSecret = process.env.PRIVY_APP_SECRET;
    if (!appId || !appSecret) {
      throw new Error('PRIVY_APP_ID and PRIVY_APP_SECRET must be set');
    }
    privyClient = new PrivyClient({ appId, appSecret });
  }
  return privyClient;
}

Then import in both routes:

import { getPrivyClient } from '@/lib/privy';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/app/api/wallet/starknet/route.ts` around lines 7 - 21,
The getPrivyClient lazy-init (privyClient and function getPrivyClient) is
duplicated across routes; extract that logic into a single shared module (e.g.,
lib/privy.ts) that exports getPrivyClient which reads
PRIVY_APP_ID/PRIVY_APP_SECRET from process.env, performs the null checks,
constructs and caches a PrivyClient, and then replace the inline
getPrivyClient/privyClient definitions in app/api/wallet/starknet/route.ts and
app/api/wallet/sign/route.ts with imports from the new shared module.
examples/privy-nextjs/app/api/wallet/sign/route.ts (1)

4-5: Use server-only environment variable naming.

NEXT_PUBLIC_PRIVY_APP_ID will be bundled into the client-side JavaScript. For server-only API routes, prefer a non-prefixed variable name (e.g., PRIVY_APP_ID) to avoid unnecessary exposure. The .env.example already defines both, but the server routes should use the non-public variant.

♻️ Proposed fix
-const PRIVY_APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID;
+const PRIVY_APP_ID = process.env.PRIVY_APP_ID;
 const PRIVY_APP_SECRET = process.env.PRIVY_APP_SECRET;

Then add PRIVY_APP_ID=your_privy_app_id to .env.example for server-side usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/app/api/wallet/sign/route.ts` around lines 4 - 5,
Replace the client-exposed env var with the server-only one: update the constant
declaration to read from process.env.PRIVY_APP_ID (instead of
NEXT_PUBLIC_PRIVY_APP_ID) in this route (the PRIVY_APP_ID constant) and ensure
the server secret uses process.env.PRIVY_APP_SECRET as before; also add
PRIVY_APP_ID=your_privy_app_id to the .env.example so developers have the
server-side variable documented.
examples/privy-nextjs/postcss.config.js (1)

1-6: Consider using ESM syntax for consistency.

The coding guidelines specify ESM-only patterns. While PostCSS config files often use CommonJS for compatibility, you could rename this to postcss.config.mjs and use ESM syntax:

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

However, this may require verification with your toolchain. As per coding guidelines: "Do not introduce CommonJS patterns; use ESM only".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/postcss.config.js` around lines 1 - 6, The PostCSS
config is using CommonJS (module.exports) which violates the ESM-only guideline;
change the file to an ESM module by renaming postcss.config.js to
postcss.config.mjs and replace the module.exports object with an export default
of the same plugins object (tailwindcss, autoprefixer), then verify your
build/toolchain accepts a .mjs PostCSS config and adjust any PostCSS loader or
Next.js config if necessary.
examples/privy-nextjs/next.config.js (1)

1-7: Consider using ESM syntax for consistency.

Per coding guidelines, ESM is preferred. Next.js supports next.config.mjs with ESM syntax:

♻️ Suggested ESM conversion

Rename to next.config.mjs:

/** `@type` {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  transpilePackages: ['starkzap'],
}

export default nextConfig

As per coding guidelines: "Do not introduce CommonJS patterns; use ESM only".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/next.config.js` around lines 1 - 7, Replace the
CommonJS export with ESM: change the file to use export default for the
nextConfig constant (the exported symbol is nextConfig) instead of
module.exports, and update the file extension/name to next.config.mjs so Next.js
loads it as ESM; keep the existing nextConfig object and properties
(reactStrictMode, transpilePackages) intact while removing the module.exports
line.
examples/privy-nextjs/lib/starkzap.ts (1)

14-18: Improve PRESETS type for better key inference.

The as const assertion has no effect after Record<string, AccountClassConfig> since Record already erases literal types. For better type safety with noUncheckedIndexedAccess, consider preserving the literal keys:

♻️ Preserve literal key types
-export const PRESETS: Record<string, AccountClassConfig> = {
+export const PRESETS = {
   argent: ArgentPreset,
   openzeppelin: OpenZeppelinPreset,
-} as const;
+} as const satisfies Record<string, AccountClassConfig>;

This preserves the literal type 'argent' | 'openzeppelin' for keys while still ensuring type compatibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/lib/starkzap.ts` around lines 14 - 18, The PRESETS
object currently uses Record<string, AccountClassConfig> which erases literal
keys and makes the trailing as const ineffective; instead, remove the explicit
Record annotation so the object literal can preserve its literal keys (e.g.,
'argent' | 'openzeppelin') and ensure type compatibility by using an "as const"
on the object plus a TypeScript satisfies clause against Record<string,
AccountClassConfig> (or an explicit union type of the allowed keys) so PRESETS
retains literal key types for safer indexed access while still conforming to
AccountClassConfig; update the declaration around PRESETS (and keep references
to ArgentPreset and OpenZeppelinPreset) accordingly.
examples/privy-nextjs/README.md (1)

76-87: Add language specifier to the fenced code block.

The architecture diagram lacks a language specifier, which triggers a markdown lint warning. Use text or plaintext for ASCII diagrams.

📝 Proposed fix
-```
+```text
 ┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/README.md` around lines 76 - 87, The fenced ASCII
diagram code block is missing a language specifier which triggers markdown lint
warnings; update the opening triple-backtick for the diagram (the block
containing the ASCII diagram with the Privy/Auth, Next.js API, Privy Server,
Starkzap SDK, and Starknet RPC boxes) to include a language specifier such as
text or plaintext (e.g., change ``` to ```text) so the lint warning is resolved.
examples/privy-nextjs/components/PrivyProvider.tsx (1)

15-24: Consider the layout impact of the missing appId fallback.

When NEXT_PUBLIC_PRIVY_APP_ID is missing, children are wrapped in a warning banner div. This changes the DOM structure compared to the Privy-enabled path, which may cause layout inconsistencies during development/preview. Consider rendering the warning as a sibling element or using a more subtle indicator.

♻️ Optional: Render warning as sibling to preserve layout
   if (!appId) {
     return (
-      <div className="p-4 bg-yellow-900/30 border border-yellow-700 rounded-lg">
-        <p className="text-yellow-400">
-          Missing NEXT_PUBLIC_PRIVY_APP_ID. Please set up your .env.local file.
-        </p>
-        {children}
-      </div>
+      <>
+        <div className="fixed top-0 left-0 right-0 p-2 bg-yellow-900/80 border-b border-yellow-700 z-50 text-center">
+          <p className="text-yellow-400 text-sm">
+            Missing NEXT_PUBLIC_PRIVY_APP_ID. Please set up your .env.local file.
+          </p>
+        </div>
+        {children}
+      </>
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/components/PrivyProvider.tsx` around lines 15 - 24, The
current PrivyProvider returns a warning div that wraps children when appId is
missing, altering the DOM tree and potentially shifting layout; update
PrivyProvider so the warning is rendered as a sibling to children (e.g., return
a fragment with children plus the warning element) or render a non-wrapping
indicator (toast/absolute banner) so the children DOM structure remains
identical to the enabled path; locate the conditional that checks appId in the
PrivyProvider component and change the return to preserve children as top-level
content while emitting the warning alongside instead of around children.
examples/privy-nextjs/tailwind.config.js (1)

1-20: CommonJS syntax used in configuration file.

This file uses module.exports (CommonJS), while the coding guidelines specify ESM only. Tailwind CSS supports ESM in .mjs or .ts config files with Next.js 14+.

Consider renaming to tailwind.config.ts for TypeScript support and ESM compliance, though this is optional since CommonJS is the conventional format for Tailwind configs.

As per coding guidelines: "Do not introduce CommonJS patterns; use ESM only" for **/*.{js,ts,tsx}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/tailwind.config.js` around lines 1 - 20, The config
uses CommonJS (module.exports) which violates the ESM-only guideline; convert it
to an ESM export and rename the file to an ESM-friendly name (e.g.,
tailwind.config.ts or tailwind.config.mjs). Replace the module.exports pattern
with an ESM default export (e.g., export default { ... } or export default /**
`@type` {import('tailwindcss').Config} */ ({ ... })) and optionally use tailwind's
defineConfig helper if using TypeScript, ensuring the same content,
theme.extend.colors (starknet) and plugins are preserved.
examples/privy-nextjs/components/StarknetWallet.tsx (1)

117-165: Consider adding a timeout for transaction confirmations (optional).

Both handleDeploy and handleTestTransfer call tx.wait() without a timeout. On testnet, transactions can occasionally get stuck, leaving the UI in a perpetual loading state. For an example app this is acceptable, but a timeout would improve UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/privy-nextjs/components/StarknetWallet.tsx` around lines 117 - 165,
handleDeploy and handleTestTransfer call tx.wait() with no timeout so the UI can
hang if a tx never confirms; wrap the wait call in a cancellable timeout (e.g.,
Promise.race between tx.wait() and a timeout Promise) and reject/throw after a
reasonable period (e.g., 30–120s) so the catch block runs, setting setError and
setLoading(false); update both functions (referencing handleDeploy,
handleTestTransfer and the tx.wait() usage) to use this timeout pattern and
ensure the timeout error message is clear so the UI can show a timeout-specific
error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/privy-nextjs/app/api/wallet/sign/route.ts`:
- Around line 54-57: The endpoint currently calls
privy.wallets().rawSign(walletId, ...) without verifying ownership of walletId;
update the handler that extracts walletId to first validate that the
authenticated user (from your session/token) owns that wallet by calling the
Privy wallets API (e.g., privy.wallets().list(...) or privy.wallets().get(...)
to fetch the wallet and compare its owner/user id to the authenticated user's
id) and return a 403/unauthorized response if there is no match before invoking
rawSign; reference the walletId variable, the rawSign call, and the route
handler to locate where to add this check.

In `@examples/privy-nextjs/components/StarknetWallet.tsx`:
- Around line 48-70: connectStarknetWallet calls getAccessToken() which can
return null, so the Authorization header may become "Bearer null"; update
connectStarknetWallet to check the token after await getAccessToken() and handle
the null case (throw an explicit error or bail out) before calling sdk.onboard,
and only set headers when token is non-null (or provide an alternative flow).
Reference symbols: connectStarknetWallet, getAccessToken, token, sdk.onboard,
and the privy.resolve headers function.

---

Nitpick comments:
In `@examples/privy-nextjs/app/api/wallet/sign/route.ts`:
- Around line 4-5: Replace the client-exposed env var with the server-only one:
update the constant declaration to read from process.env.PRIVY_APP_ID (instead
of NEXT_PUBLIC_PRIVY_APP_ID) in this route (the PRIVY_APP_ID constant) and
ensure the server secret uses process.env.PRIVY_APP_SECRET as before; also add
PRIVY_APP_ID=your_privy_app_id to the .env.example so developers have the
server-side variable documented.

In `@examples/privy-nextjs/app/api/wallet/starknet/route.ts`:
- Around line 4-5: The constant PRIVY_APP_ID is reading a client-exposed env var
(NEXT_PUBLIC_PRIVY_APP_ID) in server-side code; change it to read a server-only
variable instead (e.g., process.env.PRIVY_APP_ID) and ensure PRIVY_APP_SECRET
remains server-only, then update any usages of PRIVY_APP_ID in this file
(route.ts) to match the new variable name and adjust your environment
configuration (.env) to define PRIVY_APP_ID rather than
NEXT_PUBLIC_PRIVY_APP_ID.
- Around line 23-29: The in-memory walletStore map is unsafe for serverless cold
starts and can cause duplicate wallets (StarknetWallet.tsx posts without GET);
modify the module that declares walletStore to detect production and either log
a prominent warning or throw to prevent deployment without a persistent DB:
check process.env.NODE_ENV === 'production' near the walletStore declaration and
emit a clear console.warn (including guidance to use Redis/Postgres/Mongo) or
throw an Error to fail startup in prod builds; reference the walletStore symbol
and the StarknetWallet.tsx behavior in the message so callers know to add a
persistent store.
- Around line 7-21: The getPrivyClient lazy-init (privyClient and function
getPrivyClient) is duplicated across routes; extract that logic into a single
shared module (e.g., lib/privy.ts) that exports getPrivyClient which reads
PRIVY_APP_ID/PRIVY_APP_SECRET from process.env, performs the null checks,
constructs and caches a PrivyClient, and then replace the inline
getPrivyClient/privyClient definitions in app/api/wallet/starknet/route.ts and
app/api/wallet/sign/route.ts with imports from the new shared module.

In `@examples/privy-nextjs/components/PrivyProvider.tsx`:
- Around line 15-24: The current PrivyProvider returns a warning div that wraps
children when appId is missing, altering the DOM tree and potentially shifting
layout; update PrivyProvider so the warning is rendered as a sibling to children
(e.g., return a fragment with children plus the warning element) or render a
non-wrapping indicator (toast/absolute banner) so the children DOM structure
remains identical to the enabled path; locate the conditional that checks appId
in the PrivyProvider component and change the return to preserve children as
top-level content while emitting the warning alongside instead of around
children.

In `@examples/privy-nextjs/components/StarknetWallet.tsx`:
- Around line 117-165: handleDeploy and handleTestTransfer call tx.wait() with
no timeout so the UI can hang if a tx never confirms; wrap the wait call in a
cancellable timeout (e.g., Promise.race between tx.wait() and a timeout Promise)
and reject/throw after a reasonable period (e.g., 30–120s) so the catch block
runs, setting setError and setLoading(false); update both functions (referencing
handleDeploy, handleTestTransfer and the tx.wait() usage) to use this timeout
pattern and ensure the timeout error message is clear so the UI can show a
timeout-specific error.

In `@examples/privy-nextjs/lib/starkzap.ts`:
- Around line 14-18: The PRESETS object currently uses Record<string,
AccountClassConfig> which erases literal keys and makes the trailing as const
ineffective; instead, remove the explicit Record annotation so the object
literal can preserve its literal keys (e.g., 'argent' | 'openzeppelin') and
ensure type compatibility by using an "as const" on the object plus a TypeScript
satisfies clause against Record<string, AccountClassConfig> (or an explicit
union type of the allowed keys) so PRESETS retains literal key types for safer
indexed access while still conforming to AccountClassConfig; update the
declaration around PRESETS (and keep references to ArgentPreset and
OpenZeppelinPreset) accordingly.

In `@examples/privy-nextjs/next.config.js`:
- Around line 1-7: Replace the CommonJS export with ESM: change the file to use
export default for the nextConfig constant (the exported symbol is nextConfig)
instead of module.exports, and update the file extension/name to next.config.mjs
so Next.js loads it as ESM; keep the existing nextConfig object and properties
(reactStrictMode, transpilePackages) intact while removing the module.exports
line.

In `@examples/privy-nextjs/postcss.config.js`:
- Around line 1-6: The PostCSS config is using CommonJS (module.exports) which
violates the ESM-only guideline; change the file to an ESM module by renaming
postcss.config.js to postcss.config.mjs and replace the module.exports object
with an export default of the same plugins object (tailwindcss, autoprefixer),
then verify your build/toolchain accepts a .mjs PostCSS config and adjust any
PostCSS loader or Next.js config if necessary.

In `@examples/privy-nextjs/README.md`:
- Around line 76-87: The fenced ASCII diagram code block is missing a language
specifier which triggers markdown lint warnings; update the opening
triple-backtick for the diagram (the block containing the ASCII diagram with the
Privy/Auth, Next.js API, Privy Server, Starkzap SDK, and Starknet RPC boxes) to
include a language specifier such as text or plaintext (e.g., change ``` to
```text) so the lint warning is resolved.

In `@examples/privy-nextjs/tailwind.config.js`:
- Around line 1-20: The config uses CommonJS (module.exports) which violates the
ESM-only guideline; convert it to an ESM export and rename the file to an
ESM-friendly name (e.g., tailwind.config.ts or tailwind.config.mjs). Replace the
module.exports pattern with an ESM default export (e.g., export default { ... }
or export default /** `@type` {import('tailwindcss').Config} */ ({ ... })) and
optionally use tailwind's defineConfig helper if using TypeScript, ensuring the
same content, theme.extend.colors (starknet) and plugins are preserved.

In `@examples/privy-nextjs/tsconfig.json`:
- Around line 2-22: The tsconfig.json compilerOptions is missing two stricter
flags required by our guidelines; update the compilerOptions (in the same object
where "strict" is set) to add "noUncheckedIndexedAccess": true and
"exactOptionalPropertyTypes": true so TypeScript enforces safer indexed access
and exact optional property semantics across the codebase.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0b6d9705-e2dc-4c2a-94de-1e6ff8be8c6f

📥 Commits

Reviewing files that changed from the base of the PR and between be6e9d1 and fc0adbf.

📒 Files selected for processing (18)
  • examples/privy-nextjs/.env.example
  • examples/privy-nextjs/.gitignore
  • examples/privy-nextjs/README.md
  • examples/privy-nextjs/app/api/wallet/sign/route.ts
  • examples/privy-nextjs/app/api/wallet/starknet/route.ts
  • examples/privy-nextjs/app/error.tsx
  • examples/privy-nextjs/app/globals.css
  • examples/privy-nextjs/app/layout.tsx
  • examples/privy-nextjs/app/not-found.tsx
  • examples/privy-nextjs/app/page.tsx
  • examples/privy-nextjs/components/PrivyProvider.tsx
  • examples/privy-nextjs/components/StarknetWallet.tsx
  • examples/privy-nextjs/lib/starkzap.ts
  • examples/privy-nextjs/next.config.js
  • examples/privy-nextjs/package.json
  • examples/privy-nextjs/postcss.config.js
  • examples/privy-nextjs/tailwind.config.js
  • examples/privy-nextjs/tsconfig.json

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/types/address-like.ts`:
- Around line 10-20: The JSDoc examples and getAddress implementation are
incorrect for the branded Address type: AddressLike is defined as Address |
WalletInterface where Address is a branded string, so plain string literals
won't type-check; update examples to use fromAddress("0x123...") instead of raw
"0x123..." in the examples referencing AddressLike and adjust getAddress to
either accept string in the union (Address | string | WalletInterface) or
validate/convert incoming strings via fromAddress before casting to Address;
touch the AddressLike type, the example block, and the getAddress function to
ensure strings are validated with fromAddress (or widen the union) and that no
unvalidated cast to Address remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 947d08c2-8b26-404d-af34-5b5a1e21ad5a

📥 Commits

Reviewing files that changed from the base of the PR and between fc0adbf and 36290c7.

📒 Files selected for processing (4)
  • src/erc20/erc20.ts
  • src/staking/staking.ts
  • src/types/address-like.ts
  • src/types/index.ts

Copy link

@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

♻️ Duplicate comments (1)
src/types/address-like.ts (1)

1-41: ⚠️ Potential issue | 🟠 Major

Keep AddressLike branded instead of widening it to raw string.

This shared type now lets arbitrary strings flow through getAddress() into src/erc20/erc20.ts and src/staking/staking.ts, which drops the SDK's compile-time address guarantee and moves bad-input handling to runtime. Keep the public union as Address | WalletInterface and return Address from getAddress(); callers that start from untyped input should normalize with fromAddress() first.

♻️ Proposed fix
+import type { Address } from "@/types/address";
 import type { WalletInterface } from "@/wallet";
@@
-export type AddressLike = string | WalletInterface;
+export type AddressLike = Address | WalletInterface;
@@
-export function getAddress(addressLike: AddressLike): string {
+export function getAddress(addressLike: AddressLike): Address {
   if (typeof addressLike === "string") {
     return addressLike;
   }
   return addressLike.address;
 }

As per coding guidelines, "Preserve strict TypeScript typing; prefer explicit domain types (Address, Amount, ChainId) over primitives".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/address-like.ts` around lines 1 - 41, Change the public AddressLike
union to preserve the branded Address type instead of raw string: export type
AddressLike = Address | WalletInterface (replace current string union), update
getAddress(addressLike: AddressLike): Address to return Address and ensure its
implementation extracts addressLike when WalletInterface or simply returns the
Address when already branded; update imports to reference Address and keep
WalletInterface, and add a note in callers (e.g., users of getAddress in erc20
or staking) to normalize raw strings with fromAddress() before passing into
AddressLike so unbranded strings cannot flow through.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/privy-nextjs/app/api/wallet/sign/route.ts`:
- Around line 40-67: The rawSign call is missing the verified user JWT so the
SDK can't authorize the signing; update the rawSign invocation (function
rawSign) to pass the token variable as the authorization context by supplying {
user_jwts: [token] } as the second argument and move the params ({ hash }) into
the third options argument so the call becomes rawSign(walletId, { user_jwts:
[token] }, { params: { hash } }) while keeping the ownership checks (claims,
userWallets) intact.

In `@examples/privy-nextjs/components/StarknetWallet.tsx`:
- Around line 87-109: The auto-connect effect (useEffect that checks
authenticated && !starknetWallet and calls
ensureStarknetWallet/connectStarknetWallet/checkDeployment) can re-run if you
clear starknetWallet before Privy flips authenticated to false; add a separate
useEffect watching authenticated and when authenticated === false clear local
session state by calling setStarknetWallet(null), setPrivyWalletData(null) and
setIsDeployed(null) so state is only cleared after Privy reports logout,
preventing the immediate reconnect loop caused by calling logout() after
clearing the wallet.
- Around line 49-69: The headers closure inside connectStarknetWallet's onboard
privy.resolve currently returns a captured token and must be changed to an async
function that re-fetches the access token per signing request; update the
headers property returned by privy.resolve (the function inside
connectStarknetWallet) to be async and call getAccessToken() each time so the
Authorization header uses the fresh token (this matches PrivySigner async header
behavior).

---

Duplicate comments:
In `@src/types/address-like.ts`:
- Around line 1-41: Change the public AddressLike union to preserve the branded
Address type instead of raw string: export type AddressLike = Address |
WalletInterface (replace current string union), update getAddress(addressLike:
AddressLike): Address to return Address and ensure its implementation extracts
addressLike when WalletInterface or simply returns the Address when already
branded; update imports to reference Address and keep WalletInterface, and add a
note in callers (e.g., users of getAddress in erc20 or staking) to normalize raw
strings with fromAddress() before passing into AddressLike so unbranded strings
cannot flow through.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae605d06-019e-461b-a476-e46aee1f28f8

📥 Commits

Reviewing files that changed from the base of the PR and between 36290c7 and 572eb36.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • examples/privy-nextjs/app/api/wallet/sign/route.ts
  • examples/privy-nextjs/components/StarknetWallet.tsx
  • src/types/address-like.ts

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/privy-nextjs/app/api/wallet/sign/route.ts`:
- Around line 54-72: The rawSign call is using an invalid second argument {
user_jwts: [token] }—remove that parameter and call privy.wallets().rawSign with
the correct signature that accepts only the options object containing params
(i.e., rawSign(walletId, { params: { hash } })); ensure any user JWT context is
provided via PrivyClient initialization rather than passing user_jwts to
rawSign, and update the call in the route handler where privy.wallets().rawSign
is invoked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c879db8-c46e-4ec3-9a31-14f6ffd3cfc4

📥 Commits

Reviewing files that changed from the base of the PR and between 572eb36 and f0ab079.

📒 Files selected for processing (2)
  • examples/privy-nextjs/app/api/wallet/sign/route.ts
  • examples/privy-nextjs/components/StarknetWallet.tsx

spiritclawd pushed a commit to spiritclawd/starkzap that referenced this pull request Mar 7, 2026
Remove invalid user_jwts parameter from rawSign call.
The official @privy-io/node SDK signature is:
privy.wallets().rawSign(walletId, { params: { hash } })

Authorization for user-owned wallets is handled via PrivyClient
initialization (appSecret), not through rawSign parameters.

Fixes CodeRabbit review feedback on PR keep-starknet-strange#63.
- Add intentionalDisconnect flag to prevent disconnect race condition
- Clear wallet state BEFORE logout to avoid useEffect trigger
- Reset flag when auth state changes to allow fresh login
- Fix headers function to fetch fresh token on each signing request
- Add privy-nextjs config files to eslint ignores

Addresses CodeRabbit review comments on PR keep-starknet-strange#63.
- Use server-only PRIVY_APP_ID env var in API routes (not NEXT_PUBLIC_)
- Extract shared Privy client to lib/privy.ts module
- Add production warning for in-memory wallet storage
- Update .env.example with both client and server env vars
@0xLucqs
Copy link
Contributor

0xLucqs commented Mar 9, 2026

thanks for the PR but i don't think we'll be adding another example. What does this add compared to the 3 others we have ?

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.

docs: Privy v3 integration example needed — OnboardPrivyOptions.privy.resolve API is underdocumented

2 participants