Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 218edc3

Browse files
[SDK] Rename verifyPayment() to processPayment() for x402 payments
1 parent a81899d commit 218edc3

File tree

10 files changed

+575
-392
lines changed

10 files changed

+575
-392
lines changed

.changeset/some-moons-burn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"thirdweb": minor
33
---
44

5-
Accept arbitrary chain ids for x402 payments with new verifyPayment() backend utility
5+
Accept arbitrary chain ids for x402 payments with new settlePayment() and verifyPayment() backend utility functions

apps/playground-web/src/app/payments/x402/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function ServerCodeExample() {
5757
className="h-full rounded-none border-none"
5858
code={`// src/middleware.ts
5959
60-
import { facilitator, verifyPayment } from "thirdweb/x402";
60+
import { facilitator, settlePayment } from "thirdweb/x402";
6161
import { createThirdwebClient } from "thirdweb";
6262
6363
const client = createThirdwebClient({ secretKey: "your-secret-key" });
@@ -71,16 +71,13 @@ export async function middleware(request: NextRequest) {
7171
const resourceUrl = request.nextUrl.toString();
7272
const paymentData = request.headers.get("X-PAYMENT");
7373
74-
const result = await verifyPayment({
74+
const result = await settlePayment({
7575
resourceUrl,
7676
method,
7777
paymentData,
7878
payTo: "0xYourWalletAddress",
7979
network: "eip155:11155111", // or any other chain id
8080
price: "$0.01", // can also be a ERC20 token amount
81-
routeConfig: {
82-
description: "Access to paid content",
83-
},
8481
facilitator: thirdwebX402Facilitator,
8582
});
8683

apps/playground-web/src/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type NextRequest, NextResponse } from "next/server";
22
import { createThirdwebClient } from "thirdweb";
33
import { arbitrumSepolia } from "thirdweb/chains";
4-
import { facilitator, verifyPayment } from "thirdweb/x402";
4+
import { facilitator, settlePayment } from "thirdweb/x402";
55

66
const client = createThirdwebClient({
77
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
@@ -26,7 +26,7 @@ export async function middleware(request: NextRequest) {
2626
const resourceUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}${pathname}`;
2727
const paymentData = request.headers.get("X-PAYMENT");
2828

29-
const result = await verifyPayment({
29+
const result = await settlePayment({
3030
resourceUrl,
3131
method,
3232
paymentData,

apps/portal/src/app/payments/x402/page.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ const response = await fetchWithPay('https://api.example.com/paid-endpoint');
4141

4242
## Server Side
4343

44-
To make your API calls payable, you can use the `verifyPayment` function in a simple middleware or in your endpoint directly.
44+
To make your API calls payable, you can use the `settlePayment` function in a middleware or in your endpoint directly.
4545

46-
Use the `facilitator` configuration function settle transactions with your thirdweb server wallet gaslessly and pass it to the `verifyPayment` function.
46+
Use the `facilitator` configuration function settle transactions with your thirdweb server wallet gaslessly and pass it to the `settlePayment` function.
4747

4848
Here's an example with a Next.js middleware:
4949

5050
```typescript
5151
import { createThirdwebClient } from "thirdweb";
52-
import { facilitator, verifyPayment } from "thirdweb/x402";
52+
import { facilitator, settlePayment } from "thirdweb/x402";
5353

5454
const client = createThirdwebClient({ secretKey: "your-secret-key" });
5555
const thirdwebX402Facilitator = facilitator({
@@ -62,7 +62,7 @@ export async function middleware(request: NextRequest) {
6262
const resourceUrl = request.nextUrl.toString();
6363
const paymentData = request.headers.get("X-PAYMENT");
6464

65-
const result = await verifyPayment({
65+
const result = await settlePayment({
6666
resourceUrl,
6767
method,
6868
paymentData,
@@ -97,3 +97,5 @@ export const config = {
9797
matcher: ["/api/paid-endpoint"],
9898
};
9999
```
100+
101+
You can also use the `verifyPayment` function to verify the payment before settling it. This lets you do the work that requires payment first and then settle the payment.

packages/thirdweb/src/exports/x402.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ export {
44
type ThirdwebX402FacilitatorConfig,
55
} from "../x402/facilitator.js";
66
export { wrapFetchWithPayment } from "../x402/fetchWithPayment.js";
7-
export {
8-
type VerifyPaymentArgs,
9-
type VerifyPaymentResult,
10-
verifyPayment,
11-
} from "../x402/verify-payment.js";
7+
export { settlePayment } from "../x402/settle-payment.js";
8+
export type {
9+
PaymentArgs,
10+
SettlePaymentResult,
11+
VerifyPaymentResult,
12+
} from "../x402/types.js";
13+
export { verifyPayment } from "../x402/verify-payment.js";
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import {
2+
type ERC20TokenAmount,
3+
type Money,
4+
moneySchema,
5+
type Network,
6+
SupportedEVMNetworks,
7+
} from "x402/types";
8+
import { getAddress } from "../utils/address.js";
9+
import { decodePayment } from "./encode.js";
10+
import type { facilitator as facilitatorType } from "./facilitator.js";
11+
import {
12+
type FacilitatorNetwork,
13+
networkToChainId,
14+
type RequestedPaymentPayload,
15+
type RequestedPaymentRequirements,
16+
} from "./schemas.js";
17+
import {
18+
type PaymentArgs,
19+
type PaymentRequiredResult,
20+
x402Version,
21+
} from "./types.js";
22+
23+
type GetPaymentRequirementsResult = {
24+
status: 200;
25+
paymentRequirements: RequestedPaymentRequirements[];
26+
selectedPaymentRequirements: RequestedPaymentRequirements;
27+
decodedPayment: RequestedPaymentPayload;
28+
};
29+
30+
/**
31+
* Decodes a payment request and returns the payment requirements, selected payment requirements, and decoded payment
32+
* @param args
33+
* @returns The payment requirements, selected payment requirements, and decoded payment
34+
*/
35+
export async function decodePaymentRequest(
36+
args: PaymentArgs,
37+
): Promise<GetPaymentRequirementsResult | PaymentRequiredResult> {
38+
const {
39+
price,
40+
network,
41+
facilitator,
42+
resourceUrl,
43+
routeConfig = {},
44+
payTo,
45+
method,
46+
paymentData,
47+
} = args;
48+
const {
49+
description,
50+
mimeType,
51+
maxTimeoutSeconds,
52+
inputSchema,
53+
outputSchema,
54+
errorMessages,
55+
discoverable,
56+
} = routeConfig;
57+
const atomicAmountForAsset = await processPriceToAtomicAmount(
58+
price,
59+
network,
60+
facilitator,
61+
);
62+
if ("error" in atomicAmountForAsset) {
63+
return {
64+
status: 402,
65+
responseHeaders: { "Content-Type": "application/json" },
66+
responseBody: {
67+
x402Version,
68+
error: atomicAmountForAsset.error,
69+
accepts: [],
70+
},
71+
};
72+
}
73+
const { maxAmountRequired, asset } = atomicAmountForAsset;
74+
75+
const paymentRequirements: RequestedPaymentRequirements[] = [];
76+
77+
if (
78+
SupportedEVMNetworks.includes(network as Network) ||
79+
network.startsWith("eip155:")
80+
) {
81+
paymentRequirements.push({
82+
scheme: "exact",
83+
network,
84+
maxAmountRequired,
85+
resource: resourceUrl,
86+
description: description ?? "",
87+
mimeType: mimeType ?? "application/json",
88+
payTo: getAddress(payTo),
89+
maxTimeoutSeconds: maxTimeoutSeconds ?? 300,
90+
asset: getAddress(asset.address),
91+
// TODO: Rename outputSchema to requestStructure
92+
outputSchema: {
93+
input: {
94+
type: "http",
95+
method,
96+
discoverable: discoverable ?? true,
97+
...inputSchema,
98+
},
99+
output: outputSchema,
100+
},
101+
extra: (asset as ERC20TokenAmount["asset"]).eip712,
102+
});
103+
} else {
104+
return {
105+
status: 402,
106+
responseHeaders: {
107+
"Content-Type": "application/json",
108+
},
109+
responseBody: {
110+
x402Version,
111+
error: `Unsupported network: ${network}`,
112+
accepts: paymentRequirements,
113+
},
114+
};
115+
}
116+
117+
// Check for payment header
118+
if (!paymentData) {
119+
return {
120+
status: 402,
121+
responseHeaders: {
122+
"Content-Type": "application/json",
123+
},
124+
responseBody: {
125+
x402Version,
126+
error: errorMessages?.paymentRequired || "X-PAYMENT header is required",
127+
accepts: paymentRequirements,
128+
},
129+
};
130+
}
131+
132+
// Verify payment
133+
let decodedPayment: RequestedPaymentPayload;
134+
try {
135+
decodedPayment = decodePayment(paymentData);
136+
decodedPayment.x402Version = x402Version;
137+
} catch (error) {
138+
return {
139+
status: 402,
140+
responseHeaders: {
141+
"Content-Type": "application/json",
142+
},
143+
responseBody: {
144+
x402Version,
145+
error:
146+
errorMessages?.invalidPayment ||
147+
(error instanceof Error ? error.message : "Invalid payment"),
148+
accepts: paymentRequirements,
149+
},
150+
};
151+
}
152+
153+
const selectedPaymentRequirements = paymentRequirements.find(
154+
(value) =>
155+
value.scheme === decodedPayment.scheme &&
156+
value.network === decodedPayment.network,
157+
);
158+
if (!selectedPaymentRequirements) {
159+
return {
160+
status: 402,
161+
responseHeaders: {
162+
"Content-Type": "application/json",
163+
},
164+
responseBody: {
165+
x402Version,
166+
error:
167+
errorMessages?.noMatchingRequirements ||
168+
"Unable to find matching payment requirements",
169+
accepts: paymentRequirements,
170+
},
171+
};
172+
}
173+
174+
return {
175+
status: 200,
176+
paymentRequirements,
177+
decodedPayment,
178+
selectedPaymentRequirements,
179+
};
180+
}
181+
182+
/**
183+
* Parses the amount from the given price
184+
*
185+
* @param price - The price to parse
186+
* @param network - The network to get the default asset for
187+
* @returns The parsed amount or an error message
188+
*/
189+
async function processPriceToAtomicAmount(
190+
price: Money | ERC20TokenAmount,
191+
network: FacilitatorNetwork,
192+
facilitator: ReturnType<typeof facilitatorType>,
193+
): Promise<
194+
| { maxAmountRequired: string; asset: ERC20TokenAmount["asset"] }
195+
| { error: string }
196+
> {
197+
// Handle USDC amount (string) or token amount (ERC20TokenAmount)
198+
let maxAmountRequired: string;
199+
let asset: ERC20TokenAmount["asset"];
200+
201+
if (typeof price === "string" || typeof price === "number") {
202+
// USDC amount in dollars
203+
const parsedAmount = moneySchema.safeParse(price);
204+
if (!parsedAmount.success) {
205+
return {
206+
error: `Invalid price (price: ${price}). Must be in the form "$3.10", 0.10, "0.001", ${parsedAmount.error}`,
207+
};
208+
}
209+
const parsedUsdAmount = parsedAmount.data;
210+
const defaultAsset = await getDefaultAsset(network, facilitator);
211+
if (!defaultAsset) {
212+
return {
213+
error: `Unable to get default asset on ${network}. Please specify an asset in the payment requirements.`,
214+
};
215+
}
216+
asset = defaultAsset;
217+
maxAmountRequired = (parsedUsdAmount * 10 ** asset.decimals).toString();
218+
} else {
219+
// Token amount in atomic units
220+
maxAmountRequired = price.amount;
221+
asset = price.asset;
222+
}
223+
224+
return {
225+
maxAmountRequired,
226+
asset,
227+
};
228+
}
229+
230+
async function getDefaultAsset(
231+
network: FacilitatorNetwork,
232+
facilitator: ReturnType<typeof facilitatorType>,
233+
): Promise<ERC20TokenAmount["asset"] | undefined> {
234+
const supportedAssets = await facilitator.supported();
235+
const chainId = networkToChainId(network);
236+
const matchingAsset = supportedAssets.kinds.find(
237+
(supported) => supported.network === `eip155:${chainId}`,
238+
);
239+
const assetConfig = matchingAsset?.extra
240+
?.defaultAsset as ERC20TokenAmount["asset"];
241+
return assetConfig;
242+
}

packages/thirdweb/src/x402/facilitator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
1919

2020
/**
2121
* Creates a facilitator for the x402 payment protocol.
22-
* You can use this with `verifyPayment` or with any x402 middleware to enable settling transactions with your thirdweb server wallet.
22+
* You can use this with `settlePayment` or with any x402 middleware to enable settling transactions with your thirdweb server wallet.
2323
*
2424
* @param config - The configuration for the facilitator
2525
* @returns a x402 compatible FacilitatorConfig

0 commit comments

Comments
 (0)