From 0fafe9ffc2ae57a54e0c48c3a0f94c7c8377b13f Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Sun, 5 Oct 2025 00:28:52 +1300 Subject: [PATCH] [thirdweb] Fix waitUntil facilitator parameter not being respected --- .changeset/ten-donuts-fail.md | 5 ++ .github/workflows/auto-assign.yml | 19 ++++ .../src/app/api/paywall/route.ts | 90 +++++++++++++++++-- apps/playground-web/src/middleware.ts | 85 ------------------ packages/thirdweb/src/x402/facilitator.ts | 10 +-- 5 files changed, 114 insertions(+), 95 deletions(-) create mode 100644 .changeset/ten-donuts-fail.md create mode 100644 .github/workflows/auto-assign.yml delete mode 100644 apps/playground-web/src/middleware.ts diff --git a/.changeset/ten-donuts-fail.md b/.changeset/ten-donuts-fail.md new file mode 100644 index 00000000000..c56f1f7ed37 --- /dev/null +++ b/.changeset/ten-donuts-fail.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Fix waitUntil facilitator param not being respected diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml new file mode 100644 index 00000000000..cdd6944e709 --- /dev/null +++ b/.github/workflows/auto-assign.yml @@ -0,0 +1,19 @@ +name: Auto Author Assign + +on: + pull_request: + types: [opened, reopened, ready_for_review] + +permissions: + pull-requests: write + +jobs: + assign-author: + runs-on: ubuntu-latest + if: | + github.event.pull_request.author_association == 'MEMBER' || + github.event.pull_request.author_association == 'OWNER' || + github.event.pull_request.author_association == 'COLLABORATOR' || + github.event.pull_request.author_association == 'CONTRIBUTOR' + steps: + - uses: toshimaru/auto-author-assign@16f0022cf3d7970c106d8d1105f75a1165edb516 # v2.1.1 diff --git a/apps/playground-web/src/app/api/paywall/route.ts b/apps/playground-web/src/app/api/paywall/route.ts index 15ec04f548e..f32638cd635 100644 --- a/apps/playground-web/src/app/api/paywall/route.ts +++ b/apps/playground-web/src/app/api/paywall/route.ts @@ -1,10 +1,90 @@ -import { NextResponse } from "next/server"; +import { type NextRequest, NextResponse } from "next/server"; +import { createThirdwebClient, defineChain } from "thirdweb"; +import { toUnits } from "thirdweb/utils"; +import { facilitator, settlePayment } from "thirdweb/x402"; +import { token } from "../../payments/x402/components/constants"; + // Allow streaming responses up to 5 minutes export const maxDuration = 300; -export async function GET(_req: Request) { - return NextResponse.json({ - success: true, - message: "Payment successful. You have accessed the protected route.", +export async function GET(request: NextRequest) { + 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 twFacilitator = facilitator({ + baseUrl: `${API_URL}/v1/payments/x402`, + client, + serverWalletAddress: BACKEND_WALLET_ADDRESS, + vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, + }); + + const paymentData = request.headers.get("X-PAYMENT"); + const queryParams = request.nextUrl.searchParams; + + const chainId = queryParams.get("chainId"); + + if (!chainId) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 }, + ); + } + + const amount = queryParams.get("amount") || "0.01"; + const tokenAddress = queryParams.get("tokenAddress") || token.address; + const decimals = queryParams.get("decimals") || token.decimals.toString(); + const waitUntil = + (queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") || + "simulated"; + + const result = await settlePayment({ + resourceUrl: "https://playground-web.thirdweb.com/api/paywall", + method: "GET", + paymentData, + network: defineChain(Number(chainId)), + price: { + amount: toUnits(amount, parseInt(decimals)).toString(), + asset: { + address: tokenAddress as `0x${string}`, + decimals: decimals ? parseInt(decimals) : token.decimals, + }, + }, + routeConfig: { + description: "Access to paid content", + }, + waitUntil, + facilitator: twFacilitator, + }); + + if (result.status === 200) { + // payment successful, execute the request + return NextResponse.json( + { + success: true, + message: "Payment successful. You have accessed the protected route.", + payment: { + amount, + tokenAddress, + }, + receipt: result.paymentReceipt, + }, + { + status: 200, + headers: result.responseHeaders, + }, + ); + } + + // otherwise, request payment + return NextResponse.json(result.responseBody, { + status: result.status, + headers: result.responseHeaders, }); } diff --git a/apps/playground-web/src/middleware.ts b/apps/playground-web/src/middleware.ts deleted file mode 100644 index f7b64fb2299..00000000000 --- a/apps/playground-web/src/middleware.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { type NextRequest, NextResponse } from "next/server"; -import { createThirdwebClient, defineChain } from "thirdweb"; -import { toUnits } from "thirdweb/utils"; -import { facilitator, settlePayment } from "thirdweb/x402"; -import { token } from "./app/payments/x402/components/constants"; - -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 twFacilitator = facilitator({ - baseUrl: `${API_URL}/v1/payments/x402`, - client, - serverWalletAddress: BACKEND_WALLET_ADDRESS, - vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, -}); - -export async function middleware(request: NextRequest) { - const pathname = request.nextUrl.pathname; - const method = request.method.toUpperCase(); - const resourceUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}${pathname}`; - const paymentData = request.headers.get("X-PAYMENT"); - const queryParams = request.nextUrl.searchParams; - - const chainId = queryParams.get("chainId"); - - if (!chainId) { - return NextResponse.json( - { error: "Missing required parameters" }, - { status: 400 }, - ); - } - - const amount = queryParams.get("amount") || "0.01"; - const tokenAddress = queryParams.get("tokenAddress") || token.address; - const decimals = queryParams.get("decimals") || token.decimals.toString(); - const waitUntil = - (queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") || - "simulated"; - - const result = await settlePayment({ - resourceUrl, - method, - paymentData, - network: defineChain(Number(chainId)), - price: { - amount: toUnits(amount, parseInt(decimals)).toString(), - asset: { - address: tokenAddress as `0x${string}`, - decimals: decimals ? parseInt(decimals) : token.decimals, - }, - }, - routeConfig: { - description: "Access to paid content", - }, - waitUntil, - facilitator: twFacilitator, - }); - - if (result.status === 200) { - // payment successful, execute the request - const response = NextResponse.next(); - for (const [key, value] of Object.entries(result.responseHeaders)) { - response.headers.set(key, value); - } - return response; - } - - // otherwise, request payment - return NextResponse.json(result.responseBody, { - status: result.status, - headers: result.responseHeaders, - }); -} - -// Configure which paths the middleware should run on -export const config = { - matcher: ["/api/paywall"], -}; diff --git a/packages/thirdweb/src/x402/facilitator.ts b/packages/thirdweb/src/x402/facilitator.ts index e333c392f32..0d95070e4a9 100644 --- a/packages/thirdweb/src/x402/facilitator.ts +++ b/packages/thirdweb/src/x402/facilitator.ts @@ -15,7 +15,7 @@ export type WaitUntil = "simulated" | "submitted" | "confirmed"; export type ThirdwebX402FacilitatorConfig = { client: ThirdwebClient; serverWalletAddress: string; - waitUtil?: WaitUntil; + waitUntil?: WaitUntil; vaultAccessToken?: string; baseUrl?: string; }; @@ -40,7 +40,7 @@ export type ThirdwebX402Facilitator = { settle: ( payload: RequestedPaymentPayload, paymentRequirements: RequestedPaymentRequirements, - waitUtil?: WaitUntil, + waitUntil?: WaitUntil, ) => Promise; supported: (filters?: { chainId: number; @@ -185,14 +185,14 @@ export function facilitator( async settle( payload: RequestedPaymentPayload, paymentRequirements: RequestedPaymentRequirements, - waitUtil?: WaitUntil, + waitUntil?: WaitUntil, ): Promise { const url = config.baseUrl ?? DEFAULT_BASE_URL; let headers = { "Content-Type": "application/json" }; const authHeaders = await facilitator.createAuthHeaders(); headers = { ...headers, ...authHeaders.settle }; - const waitUtilParam = waitUtil || config.waitUtil; + const waitUntilParam = waitUntil || config.waitUntil; const res = await fetch(`${url}/settle`, { method: "POST", @@ -201,7 +201,7 @@ export function facilitator( x402Version: payload.x402Version, paymentPayload: payload, paymentRequirements: paymentRequirements, - ...(waitUtilParam ? { waitUtil: waitUtilParam } : {}), + ...(waitUntilParam ? { waitUntil: waitUntilParam } : {}), }), });