-
Notifications
You must be signed in to change notification settings - Fork 618
[Playground] Add payTo parameter to X402 payment API #8231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Playground] Add payTo parameter to X402 payment API #8231
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
WalkthroughMoved thirdweb client and x402 facilitator initialization to module scope in the paywall API route, added an optional payTo query parameter threaded through settlePayment and response, and temporarily hardcoded the API base URL to localhost. Added a corresponding “Pay To Address” input and state handling in the X402 left section UI. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant UI as X402LeftSection (UI)
participant API as /api/paywall (GET)
participant X4 as x402 Facilitator
participant NW as Network/Settle
U->>UI: Enter amount, payer, payTo
UI->>API: GET /api/paywall?amount=...&payer=...&payTo=...
Note over API: thirdweb client & facilitator initialized at module scope
API->>X4: prepare settlement params (incl. payTo)
X4->>NW: settlePayment({ ..., payTo })
alt Success
NW-->>X4: settlement result
X4-->>API: payment details (incl. payTo)
API-->>UI: 200 OK { ..., payTo }
else Failure
NW-->>X4: error
X4-->>API: error
API-->>UI: 4xx/5xx error
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8231 +/- ##
=======================================
Coverage 55.02% 55.02%
=======================================
Files 919 919
Lines 60583 60583
Branches 4124 4124
=======================================
Hits 33333 33333
Misses 27146 27146
Partials 104 104
🚀 New features to boost your workflow:
|
size-limit report 📦
|
There was a problem hiding this 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
🧹 Nitpick comments (3)
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx (1)
151-165: Consider adding validation feedback.The input field implementation is consistent with existing patterns and includes helpful placeholder and helper text. However, users would benefit from visual feedback when they enter an invalid address format.
Consider adding validation state and error messaging:
<div className="flex flex-col gap-2"> <Label htmlFor={payToId}>Pay To Address</Label> <Input id={payToId} type="text" placeholder="0x..." value={options.payTo} onChange={handlePayToChange} className="bg-card" + aria-describedby={`${payToId}-description`} /> - <p className="text-sm text-muted-foreground"> + <p id={`${payToId}-description`} className="text-sm text-muted-foreground"> The wallet address that will receive the payment </p> + {options.payTo && !/^0x[a-fA-F0-9]{40}$/.test(options.payTo) && ( + <p className="text-sm text-destructive"> + Please enter a valid Ethereum address + </p> + )} </div>apps/playground-web/src/app/api/paywall/route.ts (2)
42-42: Consider validating the payTo address format.The
payToparameter extraction is correct and properly handles the optional case. However, consider adding validation to ensure it's a valid Ethereum address format before passing it tosettlePayment.Add validation after extraction:
const payTo = queryParams.get("payTo") ?? undefined; + if (payTo && !/^0x[a-fA-F0-9]{40}$/i.test(payTo)) { + return NextResponse.json( + { error: "Invalid payTo address format" }, + { status: 400 }, + ); + }This prevents invalid addresses from reaching the payment settlement logic and provides clear error messages to clients.
75-78: Include payTo in the success response for consistency.The success response includes
amountandtokenAddressbut omits thepayToparameter. For consistency and to help clients verify the payment destination, consider includingpayToin the response.Apply this diff to include
payTo:payment: { amount, tokenAddress, + ...(payTo && { payTo }), },This maintains backward compatibility by only including
payTowhen it's present.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/playground-web/src/app/api/paywall/route.ts(3 hunks)apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsxapps/playground-web/src/app/api/paywall/route.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsxapps/playground-web/src/app/api/paywall/route.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsxapps/playground-web/src/app/api/paywall/route.ts
🧬 Code graph analysis (2)
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx (1)
packages/thirdweb/src/react/web/ui/components/formElements.tsx (1)
Label(20-28)
apps/playground-web/src/app/api/paywall/route.ts (2)
apps/playground-web/src/app/ai/api/types.ts (1)
API_URL(1-1)packages/thirdweb/src/x402/facilitator.ts (1)
facilitator(107-262)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Size
🔇 Additional comments (1)
apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx (1)
48-48: LGTM!The
payToIddeclaration follows the established pattern and correctly uses theuseId()hook for generating a unique ID.
| const client = createThirdwebClient({ | ||
| secretKey: process.env.THIRDWEB_SECRET_KEY as string, | ||
| }); | ||
|
|
||
| const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; | ||
| // const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string; | ||
| const ENGINE_VAULT_ACCESS_TOKEN = process.env | ||
| .ENGINE_VAULT_ACCESS_TOKEN as string; | ||
| const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`; | ||
| const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string; | ||
| // const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string; | ||
| const ENGINE_VAULT_ACCESS_TOKEN = process.env | ||
| .ENGINE_VAULT_ACCESS_TOKEN as string; | ||
| // const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`; | ||
| const API_URL = "http://localhost:3030"; | ||
|
|
||
| const twFacilitator = facilitator({ | ||
| baseUrl: `${API_URL}/v1/payments/x402`, | ||
| client, | ||
| serverWalletAddress: BACKEND_WALLET_ADDRESS, | ||
| vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, | ||
| }); | ||
| const twFacilitator = facilitator({ | ||
| baseUrl: `${API_URL}/v1/payments/x402`, | ||
| client, | ||
| serverWalletAddress: BACKEND_WALLET_ADDRESS, | ||
| vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify error handling for module-scope initialization.
Moving the client and facilitator initialization to module scope is an appropriate optimization that reduces per-request overhead. However, ensure that initialization failures are handled gracefully.
Run the following script to verify error handling when environment variables are missing:
Consider adding validation at module scope to fail fast with clear error messages:
if (!process.env.THIRDWEB_SECRET_KEY) {
throw new Error("THIRDWEB_SECRET_KEY environment variable is required");
}
if (!process.env.ENGINE_BACKEND_WALLET) {
throw new Error("ENGINE_BACKEND_WALLET environment variable is required");
}🏁 Script executed:
#!/bin/bash
# Description: Check if there's error handling for missing environment variables
# Search for error handling around these environment variables
rg -nP -A5 -B5 'THIRDWEB_SECRET_KEY|ENGINE_BACKEND_WALLET|ENGINE_VAULT_ACCESS_TOKEN' apps/playground-web/src/app/api/paywall/route.ts
# Search for any try-catch or error boundaries in API routes
rg -nP -A3 'export.*async.*function.*(GET|POST)' apps/playground-web/src/app/api/ --type tsLength of output: 2016
Add validation for required environment variables
Validate and throw clear errors if THIRDWEB_SECRET_KEY, ENGINE_BACKEND_WALLET, or ENGINE_VAULT_ACCESS_TOKEN is undefined before module-scope initialization. For example:
if (!process.env.THIRDWEB_SECRET_KEY) {
throw new Error("THIRDWEB_SECRET_KEY environment variable is required");
}
// …repeat for ENGINE_BACKEND_WALLET and ENGINE_VAULT_ACCESS_TOKEN🤖 Prompt for AI Agents
In apps/playground-web/src/app/api/paywall/route.ts around lines 10 to 26, the
module currently reads process.env values at module scope without validating
them; add explicit checks at the top of the module to validate
THIRDWEB_SECRET_KEY, ENGINE_BACKEND_WALLET (or ENGINE_BACKEND_SMART_WALLET if
intended), and ENGINE_VAULT_ACCESS_TOKEN and throw clear, descriptive errors if
any are missing before creating the thirdweb client or facilitator; after
validation proceed to initialize createThirdwebClient and twFacilitator as
before so runtime failures are replaced by immediate, clear startup errors.
| // const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`; | ||
| const API_URL = "http://localhost:3030"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Hardcoded localhost URL will break in production.
The API URL is hardcoded to http://localhost:3030, which will fail in any non-local environment. This appears to be temporary test code based on the commented production URL.
Apply this diff to restore the production URL:
- // const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
- const API_URL = "http://localhost:3030";
+ const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;Alternatively, if localhost testing is needed, use an environment variable to switch between environments:
const API_URL = process.env.X402_API_URL || `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;🤖 Prompt for AI Agents
In apps/playground-web/src/app/api/paywall/route.ts around lines 18 to 19, the
API_URL is hardcoded to "http://localhost:3030", which will break in production;
revert to using the production-safe environment-based value by restoring the
original production URL construction or use an environment-switching fallback
(e.g., read a dedicated X402_API_URL first, then fall back to
NEXT_PUBLIC_API_URL or "api.thirdweb.com") so the app uses the correct API
endpoint in non-local environments.
| const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| setOptions((v) => ({ | ||
| ...v, | ||
| payTo: e.target.value as `0x${string}`, | ||
| })); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe type assertion without validation.
The type assertion as 0x${string}`` does not validate that the user input is actually a valid Ethereum address. Users can enter any string, including empty values or malformed addresses, which could lead to payment failures or incorrect routing.
Consider adding validation to ensure the address:
- Starts with "0x"
- Contains only valid hexadecimal characters
- Has the correct length (42 characters for Ethereum addresses)
Apply this diff to add basic validation:
const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ // Only update if empty or valid hex address format
+ if (value === '' || /^0x[a-fA-F0-9]{0,40}$/.test(value)) {
setOptions((v) => ({
...v,
- payTo: e.target.value as `0x${string}`,
+ payTo: value as `0x${string}`,
}));
+ }
};Alternatively, use a proper address validation library like viem's isAddress() function for comprehensive validation.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| setOptions((v) => ({ | |
| ...v, | |
| payTo: e.target.value as `0x${string}`, | |
| })); | |
| }; | |
| const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const value = e.target.value; | |
| // Only update if empty or valid hex address format | |
| if (value === '' || /^0x[a-fA-F0-9]{0,40}$/.test(value)) { | |
| setOptions((v) => ({ | |
| ...v, | |
| payTo: value as `0x${string}`, | |
| })); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx
around lines 85 to 90, the handler unsafely casts user input to the template
type `0x${string}`; replace that with proper validation before updating state:
validate the input starts with "0x", is 42 characters long, and contains only
hex characters (or call a library helper like viem.isAddress), and only call
setOptions with the validated value (or set an error/validation state when
invalid) so malformed addresses are never stored via the type assertion.

PR-Codex overview
This PR adds a new input field for a "Pay To Address" in the
X402LeftSectioncomponent and updates the API route to handle thepayToparameter in the payment request.Detailed summary
payToIdusinguseId()inX402LeftSection.handlePayToChangefunction to update thepayTooption.payTofrom query parameters.Summary by CodeRabbit