From f6a2994b9396686e6fbc0d1f1b8b59d8e7dd654c Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 3 Oct 2025 21:26:58 +1300 Subject: [PATCH] [X402] Remove payTo parameter and simplify facilitator implementation --- .github/workflows/auto-assign.yml | 19 -------------- .../x402/components/X402LeftSection.tsx | 24 ------------------ apps/playground-web/src/middleware.ts | 25 ++++++++----------- packages/thirdweb/src/x402/common.ts | 7 ++---- packages/thirdweb/src/x402/facilitator.ts | 6 ++--- packages/thirdweb/src/x402/schemas.ts | 10 ++++++++ packages/thirdweb/src/x402/settle-payment.ts | 13 +++++----- packages/thirdweb/src/x402/sign.ts | 11 +++----- packages/thirdweb/src/x402/types.ts | 9 ++++--- packages/thirdweb/src/x402/verify-payment.ts | 13 +++++----- 10 files changed, 47 insertions(+), 90 deletions(-) delete mode 100644 .github/workflows/auto-assign.yml diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml deleted file mode 100644 index 6fc6827dce9..00000000000 --- a/.github/workflows/auto-assign.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Auto Author Assign - -on: - pull_request: - types: [opened, reopened, ready_for_review, draft] - -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/payments/x402/components/X402LeftSection.tsx b/apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx index 7432d154ed1..977a73bd747 100644 --- a/apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx +++ b/apps/playground-web/src/app/payments/x402/components/X402LeftSection.tsx @@ -44,7 +44,6 @@ export function X402LeftSection(props: { const chainId = useId(); const tokenId = useId(); const amountId = useId(); - const payToId = useId(); const waitUntilId = useId(); const handleChainChange = (chainId: number) => { @@ -82,13 +81,6 @@ export function X402LeftSection(props: { })); }; - const handlePayToChange = (e: React.ChangeEvent) => { - setOptions((v) => ({ - ...v, - payTo: e.target.value as `0x${string}`, - })); - }; - const handleWaitUntilChange = ( value: "simulated" | "submitted" | "confirmed", ) => { @@ -148,22 +140,6 @@ export function X402LeftSection(props: { )} - {/* Pay To input */} -
- - -

- The wallet address that will receive the payment -

-
- {/* Wait Until selection */}
diff --git a/apps/playground-web/src/middleware.ts b/apps/playground-web/src/middleware.ts index 81ff1f1ef9d..f7b64fb2299 100644 --- a/apps/playground-web/src/middleware.ts +++ b/apps/playground-web/src/middleware.ts @@ -13,17 +13,13 @@ const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_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"}`; -function createFacilitator( - waitUntil: "simulated" | "submitted" | "confirmed" = "simulated", -) { - return facilitator({ - baseUrl: `${API_URL}/v1/payments/x402`, - client, - serverWalletAddress: BACKEND_WALLET_ADDRESS, - vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, - waitUtil: waitUntil, - }); -} + +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; @@ -33,9 +29,8 @@ export async function middleware(request: NextRequest) { const queryParams = request.nextUrl.searchParams; const chainId = queryParams.get("chainId"); - const payTo = queryParams.get("payTo"); - if (!chainId || !payTo) { + if (!chainId) { return NextResponse.json( { error: "Missing required parameters" }, { status: 400 }, @@ -53,7 +48,6 @@ export async function middleware(request: NextRequest) { resourceUrl, method, paymentData, - payTo: payTo as `0x${string}`, network: defineChain(Number(chainId)), price: { amount: toUnits(amount, parseInt(decimals)).toString(), @@ -65,7 +59,8 @@ export async function middleware(request: NextRequest) { routeConfig: { description: "Access to paid content", }, - facilitator: createFacilitator(waitUntil), + waitUntil, + facilitator: twFacilitator, }); if (result.status === 200) { diff --git a/packages/thirdweb/src/x402/common.ts b/packages/thirdweb/src/x402/common.ts index c5fa449381c..a97bb1b40db 100644 --- a/packages/thirdweb/src/x402/common.ts +++ b/packages/thirdweb/src/x402/common.ts @@ -46,7 +46,6 @@ export async function decodePaymentRequest( facilitator, resourceUrl, routeConfig = {}, - payTo, method, paymentData, } = args; @@ -105,10 +104,9 @@ export async function decodePaymentRequest( resource: resourceUrl, description: description ?? "", mimeType: mimeType ?? "application/json", - payTo: getAddress(payTo), + payTo: getAddress(facilitator.address), maxTimeoutSeconds: maxTimeoutSeconds ?? 300, asset: getAddress(asset.address), - // TODO: Rename outputSchema to requestStructure outputSchema: { input: { type: "http", @@ -119,7 +117,6 @@ export async function decodePaymentRequest( output: outputSchema, }, extra: { - facilitatorAddress: facilitator.address, ...((asset as ERC20TokenAmount["asset"]).eip712 ?? {}), }, }); @@ -139,7 +136,7 @@ export async function decodePaymentRequest( }; } - // Verify payment + // decode b64 payment let decodedPayment: RequestedPaymentPayload; try { decodedPayment = decodePayment(paymentData); diff --git a/packages/thirdweb/src/x402/facilitator.ts b/packages/thirdweb/src/x402/facilitator.ts index defa797dfda..e333c392f32 100644 --- a/packages/thirdweb/src/x402/facilitator.ts +++ b/packages/thirdweb/src/x402/facilitator.ts @@ -5,6 +5,7 @@ import { withCache } from "../utils/promise/withCache.js"; import type { FacilitatorSettleResponse, FacilitatorSupportedResponse, + FacilitatorVerifyResponse, RequestedPaymentPayload, RequestedPaymentRequirements, } from "./schemas.js"; @@ -35,7 +36,7 @@ export type ThirdwebX402Facilitator = { verify: ( payload: RequestedPaymentPayload, paymentRequirements: RequestedPaymentRequirements, - ) => Promise; + ) => Promise; settle: ( payload: RequestedPaymentPayload, paymentRequirements: RequestedPaymentRequirements, @@ -126,7 +127,6 @@ export function facilitator( }, settle: { "x-secret-key": secretKey, - "x-settlement-wallet-address": serverWalletAddress, ...(config.vaultAccessToken ? { "x-vault-access-token": config.vaultAccessToken } : {}), @@ -149,7 +149,7 @@ export function facilitator( async verify( payload: RequestedPaymentPayload, paymentRequirements: RequestedPaymentRequirements, - ): Promise { + ): Promise { const url = config.baseUrl ?? DEFAULT_BASE_URL; let headers = { "Content-Type": "application/json" }; diff --git a/packages/thirdweb/src/x402/schemas.ts b/packages/thirdweb/src/x402/schemas.ts index 5840d89cdb8..cac8a1c1798 100644 --- a/packages/thirdweb/src/x402/schemas.ts +++ b/packages/thirdweb/src/x402/schemas.ts @@ -6,6 +6,7 @@ import { PaymentRequirementsSchema, SettleResponseSchema, SupportedPaymentKindsResponseSchema, + VerifyResponseSchema, } from "x402/types"; import { z } from "zod"; import type { Chain } from "../chains/types.js"; @@ -52,11 +53,20 @@ export type RequestedPaymentRequirements = z.infer< const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({ network: FacilitatorNetworkSchema, + errorMessage: z.string().optional(), }); export type FacilitatorSettleResponse = z.infer< typeof FacilitatorSettleResponseSchema >; +const FacilitatorVerifyResponseSchema = VerifyResponseSchema.extend({ + errorMessage: z.string().optional(), +}); + +export type FacilitatorVerifyResponse = z.infer< + typeof FacilitatorVerifyResponseSchema +>; + export const SupportedSignatureTypeSchema = z.enum([ "TransferWithAuthorization", "Permit", diff --git a/packages/thirdweb/src/x402/settle-payment.ts b/packages/thirdweb/src/x402/settle-payment.ts index 809341b5e1a..863f34d3a4e 100644 --- a/packages/thirdweb/src/x402/settle-payment.ts +++ b/packages/thirdweb/src/x402/settle-payment.ts @@ -156,6 +156,7 @@ export async function settlePayment( }, }; } else { + const error = settlement.errorReason || "Settlement error"; return { status: 402, responseHeaders: { @@ -163,10 +164,9 @@ export async function settlePayment( }, responseBody: { x402Version, - error: - errorMessages?.settlementFailed || - settlement.errorReason || - "Settlement failed", + error, + errorMessage: + errorMessages?.settlementFailed || settlement.errorMessage, accepts: paymentRequirements, }, }; @@ -179,9 +179,10 @@ export async function settlePayment( }, responseBody: { x402Version, - error: + error: "Settlement error", + errorMessage: errorMessages?.settlementFailed || - (error instanceof Error ? error.message : "Settlement error"), + (error instanceof Error ? error.message : undefined), accepts: paymentRequirements, }, }; diff --git a/packages/thirdweb/src/x402/sign.ts b/packages/thirdweb/src/x402/sign.ts index 5cc145e12e8..360b4bc1958 100644 --- a/packages/thirdweb/src/x402/sign.ts +++ b/packages/thirdweb/src/x402/sign.ts @@ -230,18 +230,13 @@ async function signERC3009Authorization( async function signERC2612Permit( account: Account, - { from, value, validBefore, nonce }: ExactEvmPayloadAuthorization, + { from, to, value, validBefore, nonce }: ExactEvmPayloadAuthorization, { asset, network, extra }: RequestedPaymentRequirements, ): Promise<{ signature: Hex }> { const chainId = networkToChainId(network); const name = extra?.name; const version = extra?.version; - const facilitatorAddress = extra?.facilitatorAddress; - if (!facilitatorAddress) { - throw new Error( - "facilitatorAddress is required in PaymentRequirements extra to pay with permit-based assets", - ); - } + if (!name || !version) { throw new Error( "name and version are required in PaymentRequirements extra to pay with permit-based assets", @@ -268,7 +263,7 @@ async function signERC2612Permit( primaryType: "Permit" as const, message: { owner: getAddress(from), - spender: getAddress(facilitatorAddress), // approve the facilitator + spender: getAddress(to), value: BigInt(value), nonce: hexToBigInt(nonce as Hex), deadline: BigInt(validBefore), diff --git a/packages/thirdweb/src/x402/types.ts b/packages/thirdweb/src/x402/types.ts index 7dc463df712..66e5fe99012 100644 --- a/packages/thirdweb/src/x402/types.ts +++ b/packages/thirdweb/src/x402/types.ts @@ -1,7 +1,6 @@ import type { Money, PaymentMiddlewareConfig } from "x402/types"; import type z from "zod"; import type { Chain } from "../chains/types.js"; -import type { Address } from "../utils/address.js"; import type { Prettify } from "../utils/type-utils.js"; import type { ThirdwebX402Facilitator, WaitUntil } from "./facilitator.js"; import type { @@ -27,8 +26,6 @@ export type PaymentArgs = { method: "GET" | "POST" | ({} & string); /** The payment data/proof provided by the client, typically from the X-PAYMENT header */ paymentData?: string | null; - /** The wallet address that should receive the payment */ - payTo: Address; /** The blockchain network where the payment should be processed */ network: FacilitatorNetwork | Chain; /** The price for accessing the resource - either a USD amount (e.g., "$0.10") or a specific token amount */ @@ -37,6 +34,8 @@ export type PaymentArgs = { facilitator: ThirdwebX402Facilitator; /** Optional configuration for the payment middleware route */ routeConfig?: PaymentMiddlewareConfig; + /** @deprecated Use facilitator.address instead */ + payTo?: string; }; export type SettlePaymentArgs = PaymentArgs & { @@ -50,8 +49,10 @@ export type PaymentRequiredResult = { responseBody: { /** The X402 protocol version */ x402Version: number; - /** Human-readable error message */ + /** error code */ error: string; + /** Human-readable error message */ + errorMessage?: string; /** Array of acceptable payment methods and requirements */ accepts: RequestedPaymentRequirements[]; /** Optional payer address if verification partially succeeded */ diff --git a/packages/thirdweb/src/x402/verify-payment.ts b/packages/thirdweb/src/x402/verify-payment.ts index 09927775994..d5733c35274 100644 --- a/packages/thirdweb/src/x402/verify-payment.ts +++ b/packages/thirdweb/src/x402/verify-payment.ts @@ -101,6 +101,7 @@ export async function verifyPayment( selectedPaymentRequirements, }; } else { + const error = verification.invalidReason || "Verification failed"; return { status: 402, responseHeaders: { @@ -108,10 +109,9 @@ export async function verifyPayment( }, responseBody: { x402Version, - error: - errorMessages?.verificationFailed || - verification.invalidReason || - "Verification failed", + error: error, + errorMessage: + errorMessages?.verificationFailed || verification.errorMessage, accepts: paymentRequirements, }, }; @@ -124,9 +124,10 @@ export async function verifyPayment( }, responseBody: { x402Version, - error: + error: "Verification error", + errorMessage: errorMessages?.verificationFailed || - (error instanceof Error ? error.message : "Verification error"), + (error instanceof Error ? error.message : undefined), accepts: paymentRequirements, }, };