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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions apps/playground-web/src/app/api/paywall/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ import { token } from "../../payments/x402/components/constants";
// Allow streaming responses up to 5 minutes
export const maxDuration = 300;

export async function GET(request: NextRequest) {
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
});
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";
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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 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,
});
Comment on lines +10 to +26
Copy link
Contributor

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 ts

Length 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.


export async function GET(request: NextRequest) {
const paymentData = request.headers.get("X-PAYMENT");
const queryParams = request.nextUrl.searchParams;

Expand All @@ -38,6 +39,7 @@ export async function GET(request: NextRequest) {
}

const amount = queryParams.get("amount") || "0.01";
const payTo = queryParams.get("payTo") ?? undefined;
const tokenAddress = queryParams.get("tokenAddress") || token.address;
const decimals = queryParams.get("decimals") || token.decimals.toString();
const waitUntil =
Expand All @@ -49,6 +51,7 @@ export async function GET(request: NextRequest) {
method: "GET",
paymentData,
network: defineChain(Number(chainId)),
payTo,
price: {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function X402LeftSection(props: {
const tokenId = useId();
const amountId = useId();
const waitUntilId = useId();
const payToId = useId();

const handleChainChange = (chainId: number) => {
setSelectedChain(chainId);
Expand Down Expand Up @@ -81,6 +82,13 @@ export function X402LeftSection(props: {
}));
};

const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setOptions((v) => ({
...v,
payTo: e.target.value as `0x${string}`,
}));
};
Comment on lines +85 to +90
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.


const handleWaitUntilChange = (
value: "simulated" | "submitted" | "confirmed",
) => {
Expand Down Expand Up @@ -140,6 +148,22 @@ export function X402LeftSection(props: {
)}
</div>

{/* Pay To input */}
<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"
/>
<p className="text-sm text-muted-foreground">
The wallet address that will receive the payment
</p>
</div>

{/* Wait Until selection */}
<div className="flex flex-col gap-2">
<Label htmlFor={waitUntilId}>Wait Until</Label>
Expand Down
Loading