feat: add Privy v3 Next.js App Router example#63
feat: add Privy v3 Next.js App Router example#63spiritclawd wants to merge 5 commits intokeep-starknet-strange:mainfrom
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Warning
|
| 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
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 | 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
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:
noUncheckedIndexedAccessandexactOptionalPropertyTypes.♻️ 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,exactOptionalPropertyTypesenabled."🤖 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
walletStorewill be cleared on serverless cold starts (Vercel, AWS Lambda, etc.). While commented, users may miss this critical limitation. TheStarknetWallet.tsxcomponent 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 inapp/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_IDwill 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.examplealready 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_idto.env.examplefor 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.mjsand 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.mjswith ESM syntax:♻️ Suggested ESM conversion
Rename to
next.config.mjs:/** `@type` {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, transpilePackages: ['starkzap'], } export default nextConfigAs 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 constassertion has no effect afterRecord<string, AccountClassConfig>sinceRecordalready erases literal types. For better type safety withnoUncheckedIndexedAccess, 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
textorplaintextfor 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_IDis 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.mjsor.tsconfig files with Next.js 14+.Consider renaming to
tailwind.config.tsfor 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
handleDeployandhandleTestTransfercalltx.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
📒 Files selected for processing (18)
examples/privy-nextjs/.env.exampleexamples/privy-nextjs/.gitignoreexamples/privy-nextjs/README.mdexamples/privy-nextjs/app/api/wallet/sign/route.tsexamples/privy-nextjs/app/api/wallet/starknet/route.tsexamples/privy-nextjs/app/error.tsxexamples/privy-nextjs/app/globals.cssexamples/privy-nextjs/app/layout.tsxexamples/privy-nextjs/app/not-found.tsxexamples/privy-nextjs/app/page.tsxexamples/privy-nextjs/components/PrivyProvider.tsxexamples/privy-nextjs/components/StarknetWallet.tsxexamples/privy-nextjs/lib/starkzap.tsexamples/privy-nextjs/next.config.jsexamples/privy-nextjs/package.jsonexamples/privy-nextjs/postcss.config.jsexamples/privy-nextjs/tailwind.config.jsexamples/privy-nextjs/tsconfig.json
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
src/erc20/erc20.tssrc/staking/staking.tssrc/types/address-like.tssrc/types/index.ts
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/types/address-like.ts (1)
1-41:⚠️ Potential issue | 🟠 MajorKeep
AddressLikebranded instead of widening it to rawstring.This shared type now lets arbitrary strings flow through
getAddress()intosrc/erc20/erc20.tsandsrc/staking/staking.ts, which drops the SDK's compile-time address guarantee and moves bad-input handling to runtime. Keep the public union asAddress | WalletInterfaceand returnAddressfromgetAddress(); callers that start from untyped input should normalize withfromAddress()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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (3)
examples/privy-nextjs/app/api/wallet/sign/route.tsexamples/privy-nextjs/components/StarknetWallet.tsxsrc/types/address-like.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
examples/privy-nextjs/app/api/wallet/sign/route.tsexamples/privy-nextjs/components/StarknetWallet.tsx
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.
697c7e1 to
5d71eba
Compare
- 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
|
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 ? |
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
Files Added
app/api/wallet/starknet/route.tsapp/api/wallet/sign/route.tscomponents/PrivyProvider.tsxcomponents/StarknetWallet.tsxlib/starkzap.tsREADME.mdTesting
.env.exampleto.env.localnpm install && npm run devSummary by CodeRabbit
New Features
Refactor