thirdweb 5.107.1 → 5.108.0-nightly-a94f22928a662a5aff7a203fc2d383d9fa0907ec-20250923000340
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/exports/x402.js +6 -1
- package/dist/cjs/exports/x402.js.map +1 -1
- package/dist/cjs/react/core/utils/defaultTokens.js +2 -8
- package/dist/cjs/react/core/utils/defaultTokens.js.map +1 -1
- package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js +18 -14
- package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
- package/dist/cjs/react/web/ui/Bridge/BuyWidget.js +15 -6
- package/dist/cjs/react/web/ui/Bridge/BuyWidget.js.map +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/version.js.map +1 -1
- package/dist/cjs/x402/encode.js +71 -0
- package/dist/cjs/x402/encode.js.map +1 -0
- package/dist/cjs/x402/facilitator.js +83 -2
- package/dist/cjs/x402/facilitator.js.map +1 -1
- package/dist/cjs/x402/fetchWithPayment.js +7 -15
- package/dist/cjs/x402/fetchWithPayment.js.map +1 -1
- package/dist/cjs/x402/schemas.js +50 -0
- package/dist/cjs/x402/schemas.js.map +1 -0
- package/dist/cjs/x402/sign.js +148 -0
- package/dist/cjs/x402/sign.js.map +1 -0
- package/dist/cjs/x402/verify-payment.js +341 -0
- package/dist/cjs/x402/verify-payment.js.map +1 -0
- package/dist/esm/exports/x402.js +2 -0
- package/dist/esm/exports/x402.js.map +1 -1
- package/dist/esm/react/core/utils/defaultTokens.js +2 -8
- package/dist/esm/react/core/utils/defaultTokens.js.map +1 -1
- package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js +18 -14
- package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
- package/dist/esm/react/web/ui/Bridge/BuyWidget.js +15 -6
- package/dist/esm/react/web/ui/Bridge/BuyWidget.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/esm/x402/encode.js +66 -0
- package/dist/esm/x402/encode.js.map +1 -0
- package/dist/esm/x402/facilitator.js +83 -2
- package/dist/esm/x402/facilitator.js.map +1 -1
- package/dist/esm/x402/fetchWithPayment.js +8 -16
- package/dist/esm/x402/fetchWithPayment.js.map +1 -1
- package/dist/esm/x402/schemas.js +46 -0
- package/dist/esm/x402/schemas.js.map +1 -0
- package/dist/esm/x402/sign.js +145 -0
- package/dist/esm/x402/sign.js.map +1 -0
- package/dist/esm/x402/verify-payment.js +338 -0
- package/dist/esm/x402/verify-payment.js.map +1 -0
- package/dist/types/exports/x402.d.ts +2 -0
- package/dist/types/exports/x402.d.ts.map +1 -1
- package/dist/types/react/core/utils/defaultTokens.d.ts +2 -7
- package/dist/types/react/core/utils/defaultTokens.d.ts.map +1 -1
- package/dist/types/react/web/ui/Bridge/BridgeOrchestrator.d.ts +4 -3
- package/dist/types/react/web/ui/Bridge/BridgeOrchestrator.d.ts.map +1 -1
- package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts +7 -3
- package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.d.ts.map +1 -1
- package/dist/types/x402/encode.d.ts +23 -0
- package/dist/types/x402/encode.d.ts.map +1 -0
- package/dist/types/x402/facilitator.d.ts +44 -3
- package/dist/types/x402/facilitator.d.ts.map +1 -1
- package/dist/types/x402/fetchWithPayment.d.ts +1 -1
- package/dist/types/x402/fetchWithPayment.d.ts.map +1 -1
- package/dist/types/x402/schemas.d.ts +164 -0
- package/dist/types/x402/schemas.d.ts.map +1 -0
- package/dist/types/x402/sign.d.ts +12 -0
- package/dist/types/x402/sign.d.ts.map +1 -0
- package/dist/types/x402/verify-payment.d.ts +158 -0
- package/dist/types/x402/verify-payment.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/exports/x402.ts +6 -0
- package/src/react/core/utils/defaultTokens.ts +2 -8
- package/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +42 -31
- package/src/react/web/ui/Bridge/BuyWidget.tsx +24 -9
- package/src/version.ts +1 -1
- package/src/x402/encode.ts +81 -0
- package/src/x402/facilitator.ts +114 -6
- package/src/x402/fetchWithPayment.ts +13 -27
- package/src/x402/schemas.ts +76 -0
- package/src/x402/sign.ts +206 -0
- package/src/x402/verify-payment.ts +469 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ERC20TokenAmount,
|
|
3
|
+
type Money,
|
|
4
|
+
moneySchema,
|
|
5
|
+
type Network,
|
|
6
|
+
type PaymentMiddlewareConfig,
|
|
7
|
+
SupportedEVMNetworks,
|
|
8
|
+
} from "x402/types";
|
|
9
|
+
import { type Address, getAddress } from "../utils/address.js";
|
|
10
|
+
import { stringify } from "../utils/json.js";
|
|
11
|
+
import { decodePayment, safeBase64Encode } from "./encode.js";
|
|
12
|
+
import type { facilitator as facilitatorType } from "./facilitator.js";
|
|
13
|
+
import {
|
|
14
|
+
type FacilitatorNetwork,
|
|
15
|
+
type FacilitatorSettleResponse,
|
|
16
|
+
networkToChainId,
|
|
17
|
+
type RequestedPaymentPayload,
|
|
18
|
+
type RequestedPaymentRequirements,
|
|
19
|
+
} from "./schemas.js";
|
|
20
|
+
|
|
21
|
+
const x402Version = 1;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration object for verifying X402 payments.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type VerifyPaymentArgs = {
|
|
29
|
+
/** The URL of the resource being protected by the payment */
|
|
30
|
+
resourceUrl: string;
|
|
31
|
+
/** The HTTP method used to access the resource */
|
|
32
|
+
method: "GET" | "POST" | ({} & string);
|
|
33
|
+
/** The payment data/proof provided by the client, typically from the X-PAYMENT header */
|
|
34
|
+
paymentData?: string | null;
|
|
35
|
+
/** The wallet address that should receive the payment */
|
|
36
|
+
payTo: Address;
|
|
37
|
+
/** The blockchain network where the payment should be processed */
|
|
38
|
+
network: FacilitatorNetwork;
|
|
39
|
+
/** The price for accessing the resource - either a USD amount (e.g., "$0.10") or a specific token amount */
|
|
40
|
+
price: Money | ERC20TokenAmount;
|
|
41
|
+
/** The payment facilitator instance used to verify and settle payments */
|
|
42
|
+
facilitator: ReturnType<typeof facilitatorType>;
|
|
43
|
+
/** Optional configuration for the payment middleware route */
|
|
44
|
+
routeConfig?: PaymentMiddlewareConfig;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The result of a payment verification operation.
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export type VerifyPaymentResult =
|
|
53
|
+
| {
|
|
54
|
+
/** HTTP 200 - Payment was successfully verified and settled */
|
|
55
|
+
status: 200;
|
|
56
|
+
/** Response headers including payment receipt information */
|
|
57
|
+
responseHeaders: Record<string, string>;
|
|
58
|
+
/** The settlement receipt from the payment facilitator */
|
|
59
|
+
paymentReceipt: FacilitatorSettleResponse;
|
|
60
|
+
}
|
|
61
|
+
| {
|
|
62
|
+
/** HTTP 402 - Payment Required, verification failed or payment missing */
|
|
63
|
+
status: 402;
|
|
64
|
+
/** The error response body containing payment requirements */
|
|
65
|
+
responseBody: {
|
|
66
|
+
/** The X402 protocol version */
|
|
67
|
+
x402Version: number;
|
|
68
|
+
/** Human-readable error message */
|
|
69
|
+
error: string;
|
|
70
|
+
/** Array of acceptable payment methods and requirements */
|
|
71
|
+
accepts: RequestedPaymentRequirements[];
|
|
72
|
+
/** Optional payer address if verification partially succeeded */
|
|
73
|
+
payer?: string;
|
|
74
|
+
};
|
|
75
|
+
/** Response headers for the error response */
|
|
76
|
+
responseHeaders: Record<string, string>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Verifies and processes X402 payments for protected resources.
|
|
81
|
+
*
|
|
82
|
+
* This function implements the X402 payment protocol, verifying payment proofs
|
|
83
|
+
* and settling payments through a facilitator service. It handles the complete
|
|
84
|
+
* payment flow from validation to settlement.
|
|
85
|
+
*
|
|
86
|
+
* @param args - Configuration object containing payment verification parameters
|
|
87
|
+
* @returns A promise that resolves to either a successful payment result (200) or payment required error (402)
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* // Usage in a Next.js API route
|
|
92
|
+
* import { verifyPayment, facilitator } from "thirdweb/x402";
|
|
93
|
+
* import { createThirdwebClient } from "thirdweb";
|
|
94
|
+
*
|
|
95
|
+
* const client = createThirdwebClient({
|
|
96
|
+
* secretKey: process.env.THIRDWEB_SECRET_KEY,
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* const thirdwebFacilitator = facilitator({
|
|
100
|
+
* client,
|
|
101
|
+
* serverWalletAddress: "0x1234567890123456789012345678901234567890",
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* export async function GET(request: Request) {
|
|
105
|
+
* const paymentData = request.headers.get("x-payment");
|
|
106
|
+
*
|
|
107
|
+
* const result = await verifyPayment({
|
|
108
|
+
* resourceUrl: "https://api.example.com/premium-content",
|
|
109
|
+
* method: "GET",
|
|
110
|
+
* paymentData,
|
|
111
|
+
* payTo: "0x1234567890123456789012345678901234567890",
|
|
112
|
+
* network: "eip155:84532", // CAIP2 format: "eip155:<chain_id>"
|
|
113
|
+
* price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
|
|
114
|
+
* facilitator: thirdwebFacilitator,
|
|
115
|
+
* routeConfig: {
|
|
116
|
+
* description: "Access to premium API content",
|
|
117
|
+
* mimeType: "application/json",
|
|
118
|
+
* maxTimeoutSeconds: 300,
|
|
119
|
+
* },
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* if (result.status === 200) {
|
|
123
|
+
* // Payment verified and settled successfully
|
|
124
|
+
* return Response.json({ data: "premium content" }, {
|
|
125
|
+
* headers: result.responseHeaders,
|
|
126
|
+
* });
|
|
127
|
+
* } else {
|
|
128
|
+
* // Payment required
|
|
129
|
+
* return Response.json(result.responseBody, {
|
|
130
|
+
* status: result.status,
|
|
131
|
+
* headers: result.responseHeaders,
|
|
132
|
+
* });
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* // Usage in Express middleware
|
|
140
|
+
* import express from "express";
|
|
141
|
+
* import { verifyPayment, facilitator } from "thirdweb/x402";
|
|
142
|
+
*
|
|
143
|
+
* const app = express();
|
|
144
|
+
*
|
|
145
|
+
* async function paymentMiddleware(req, res, next) {
|
|
146
|
+
* const result = await verifyPayment({
|
|
147
|
+
* resourceUrl: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
|
|
148
|
+
* method: req.method,
|
|
149
|
+
* paymentData: req.headers["x-payment"],
|
|
150
|
+
* payTo: "0x1234567890123456789012345678901234567890",
|
|
151
|
+
* network: "eip155:8453", // CAIP2 format: "eip155:<chain_id>"
|
|
152
|
+
* price: "$0.05",
|
|
153
|
+
* facilitator: thirdwebFacilitator,
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* if (result.status === 200) {
|
|
157
|
+
* // Set payment receipt headers and continue
|
|
158
|
+
* Object.entries(result.responseHeaders).forEach(([key, value]) => {
|
|
159
|
+
* res.setHeader(key, value);
|
|
160
|
+
* });
|
|
161
|
+
* next();
|
|
162
|
+
* } else {
|
|
163
|
+
* // Return payment required response
|
|
164
|
+
* res.status(result.status)
|
|
165
|
+
* .set(result.responseHeaders)
|
|
166
|
+
* .json(result.responseBody);
|
|
167
|
+
* }
|
|
168
|
+
* }
|
|
169
|
+
*
|
|
170
|
+
* app.get("/api/premium", paymentMiddleware, (req, res) => {
|
|
171
|
+
* res.json({ message: "This is premium content!" });
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* @public
|
|
176
|
+
* @beta
|
|
177
|
+
* @bridge x402
|
|
178
|
+
*/
|
|
179
|
+
export async function verifyPayment(
|
|
180
|
+
args: VerifyPaymentArgs,
|
|
181
|
+
): Promise<VerifyPaymentResult> {
|
|
182
|
+
const {
|
|
183
|
+
price,
|
|
184
|
+
network,
|
|
185
|
+
routeConfig = {},
|
|
186
|
+
resourceUrl,
|
|
187
|
+
method,
|
|
188
|
+
payTo,
|
|
189
|
+
paymentData: paymentProof,
|
|
190
|
+
facilitator,
|
|
191
|
+
} = args;
|
|
192
|
+
const {
|
|
193
|
+
description,
|
|
194
|
+
mimeType,
|
|
195
|
+
maxTimeoutSeconds,
|
|
196
|
+
inputSchema,
|
|
197
|
+
outputSchema,
|
|
198
|
+
errorMessages,
|
|
199
|
+
discoverable,
|
|
200
|
+
} = routeConfig;
|
|
201
|
+
|
|
202
|
+
const atomicAmountForAsset = await processPriceToAtomicAmount(
|
|
203
|
+
price,
|
|
204
|
+
network,
|
|
205
|
+
facilitator,
|
|
206
|
+
);
|
|
207
|
+
if ("error" in atomicAmountForAsset) {
|
|
208
|
+
return {
|
|
209
|
+
status: 402,
|
|
210
|
+
responseHeaders: { "Content-Type": "application/json" },
|
|
211
|
+
responseBody: {
|
|
212
|
+
x402Version,
|
|
213
|
+
error: atomicAmountForAsset.error,
|
|
214
|
+
accepts: [],
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const { maxAmountRequired, asset } = atomicAmountForAsset;
|
|
219
|
+
|
|
220
|
+
const paymentRequirements: RequestedPaymentRequirements[] = [];
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
SupportedEVMNetworks.includes(network as Network) ||
|
|
224
|
+
network.startsWith("eip155:")
|
|
225
|
+
) {
|
|
226
|
+
paymentRequirements.push({
|
|
227
|
+
scheme: "exact",
|
|
228
|
+
network,
|
|
229
|
+
maxAmountRequired,
|
|
230
|
+
resource: resourceUrl,
|
|
231
|
+
description: description ?? "",
|
|
232
|
+
mimeType: mimeType ?? "application/json",
|
|
233
|
+
payTo: getAddress(payTo),
|
|
234
|
+
maxTimeoutSeconds: maxTimeoutSeconds ?? 300,
|
|
235
|
+
asset: getAddress(asset.address),
|
|
236
|
+
// TODO: Rename outputSchema to requestStructure
|
|
237
|
+
outputSchema: {
|
|
238
|
+
input: {
|
|
239
|
+
type: "http",
|
|
240
|
+
method,
|
|
241
|
+
discoverable: discoverable ?? true,
|
|
242
|
+
...inputSchema,
|
|
243
|
+
},
|
|
244
|
+
output: outputSchema,
|
|
245
|
+
},
|
|
246
|
+
extra: (asset as ERC20TokenAmount["asset"]).eip712,
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
return {
|
|
250
|
+
status: 402,
|
|
251
|
+
responseHeaders: {
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
},
|
|
254
|
+
responseBody: {
|
|
255
|
+
x402Version,
|
|
256
|
+
error: `Unsupported network: ${network}`,
|
|
257
|
+
accepts: paymentRequirements,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check for payment header
|
|
263
|
+
if (!paymentProof) {
|
|
264
|
+
return {
|
|
265
|
+
status: 402,
|
|
266
|
+
responseHeaders: {
|
|
267
|
+
"Content-Type": "application/json",
|
|
268
|
+
},
|
|
269
|
+
responseBody: {
|
|
270
|
+
x402Version,
|
|
271
|
+
error: errorMessages?.paymentRequired || "X-PAYMENT header is required",
|
|
272
|
+
accepts: paymentRequirements,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Verify payment
|
|
278
|
+
let decodedPayment: RequestedPaymentPayload;
|
|
279
|
+
try {
|
|
280
|
+
decodedPayment = decodePayment(paymentProof);
|
|
281
|
+
decodedPayment.x402Version = x402Version;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
status: 402,
|
|
285
|
+
responseHeaders: {
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
},
|
|
288
|
+
responseBody: {
|
|
289
|
+
x402Version,
|
|
290
|
+
error:
|
|
291
|
+
errorMessages?.invalidPayment ||
|
|
292
|
+
(error instanceof Error ? error.message : "Invalid payment"),
|
|
293
|
+
accepts: paymentRequirements,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const selectedPaymentRequirements = paymentRequirements.find(
|
|
299
|
+
(value) =>
|
|
300
|
+
value.scheme === decodedPayment.scheme &&
|
|
301
|
+
value.network === decodedPayment.network,
|
|
302
|
+
);
|
|
303
|
+
if (!selectedPaymentRequirements) {
|
|
304
|
+
return {
|
|
305
|
+
status: 402,
|
|
306
|
+
responseHeaders: {
|
|
307
|
+
"Content-Type": "application/json",
|
|
308
|
+
},
|
|
309
|
+
responseBody: {
|
|
310
|
+
x402Version,
|
|
311
|
+
error:
|
|
312
|
+
errorMessages?.noMatchingRequirements ||
|
|
313
|
+
"Unable to find matching payment requirements",
|
|
314
|
+
accepts: paymentRequirements,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const verification = await facilitator.verify(
|
|
321
|
+
decodedPayment,
|
|
322
|
+
selectedPaymentRequirements,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (!verification.isValid) {
|
|
326
|
+
return {
|
|
327
|
+
status: 402,
|
|
328
|
+
responseHeaders: {
|
|
329
|
+
"Content-Type": "application/json",
|
|
330
|
+
},
|
|
331
|
+
responseBody: {
|
|
332
|
+
x402Version,
|
|
333
|
+
error:
|
|
334
|
+
errorMessages?.verificationFailed ||
|
|
335
|
+
verification.invalidReason ||
|
|
336
|
+
"Payment verification failed",
|
|
337
|
+
accepts: paymentRequirements,
|
|
338
|
+
payer: verification.payer,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return {
|
|
344
|
+
status: 402,
|
|
345
|
+
responseHeaders: {
|
|
346
|
+
"Content-Type": "application/json",
|
|
347
|
+
},
|
|
348
|
+
responseBody: {
|
|
349
|
+
x402Version,
|
|
350
|
+
error:
|
|
351
|
+
errorMessages?.verificationFailed ||
|
|
352
|
+
(error instanceof Error
|
|
353
|
+
? error.message
|
|
354
|
+
: "Payment Verification error"),
|
|
355
|
+
accepts: paymentRequirements,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Settle payment
|
|
361
|
+
try {
|
|
362
|
+
const settlement = await facilitator.settle(
|
|
363
|
+
decodedPayment,
|
|
364
|
+
selectedPaymentRequirements,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (settlement.success) {
|
|
368
|
+
return {
|
|
369
|
+
status: 200,
|
|
370
|
+
paymentReceipt: settlement,
|
|
371
|
+
responseHeaders: {
|
|
372
|
+
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
|
|
373
|
+
"X-PAYMENT-RESPONSE": safeBase64Encode(stringify(settlement)),
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
} else {
|
|
377
|
+
return {
|
|
378
|
+
status: 402,
|
|
379
|
+
responseHeaders: {
|
|
380
|
+
"Content-Type": "application/json",
|
|
381
|
+
},
|
|
382
|
+
responseBody: {
|
|
383
|
+
x402Version,
|
|
384
|
+
error:
|
|
385
|
+
errorMessages?.settlementFailed ||
|
|
386
|
+
settlement.errorReason ||
|
|
387
|
+
"Settlement failed",
|
|
388
|
+
accepts: paymentRequirements,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
status: 402,
|
|
395
|
+
responseHeaders: {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
},
|
|
398
|
+
responseBody: {
|
|
399
|
+
x402Version,
|
|
400
|
+
error:
|
|
401
|
+
errorMessages?.settlementFailed ||
|
|
402
|
+
(error instanceof Error ? error.message : "Settlement error"),
|
|
403
|
+
accepts: paymentRequirements,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Parses the amount from the given price
|
|
411
|
+
*
|
|
412
|
+
* @param price - The price to parse
|
|
413
|
+
* @param network - The network to get the default asset for
|
|
414
|
+
* @returns The parsed amount or an error message
|
|
415
|
+
*/
|
|
416
|
+
async function processPriceToAtomicAmount(
|
|
417
|
+
price: Money | ERC20TokenAmount,
|
|
418
|
+
network: FacilitatorNetwork,
|
|
419
|
+
facilitator: ReturnType<typeof facilitatorType>,
|
|
420
|
+
): Promise<
|
|
421
|
+
| { maxAmountRequired: string; asset: ERC20TokenAmount["asset"] }
|
|
422
|
+
| { error: string }
|
|
423
|
+
> {
|
|
424
|
+
// Handle USDC amount (string) or token amount (ERC20TokenAmount)
|
|
425
|
+
let maxAmountRequired: string;
|
|
426
|
+
let asset: ERC20TokenAmount["asset"];
|
|
427
|
+
|
|
428
|
+
if (typeof price === "string" || typeof price === "number") {
|
|
429
|
+
// USDC amount in dollars
|
|
430
|
+
const parsedAmount = moneySchema.safeParse(price);
|
|
431
|
+
if (!parsedAmount.success) {
|
|
432
|
+
return {
|
|
433
|
+
error: `Invalid price (price: ${price}). Must be in the form "$3.10", 0.10, "0.001", ${parsedAmount.error}`,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const parsedUsdAmount = parsedAmount.data;
|
|
437
|
+
const defaultAsset = await getDefaultAsset(network, facilitator);
|
|
438
|
+
if (!defaultAsset) {
|
|
439
|
+
return {
|
|
440
|
+
error: `Unable to get default asset on ${network}. Please specify an asset in the payment requirements.`,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
asset = defaultAsset;
|
|
444
|
+
maxAmountRequired = (parsedUsdAmount * 10 ** asset.decimals).toString();
|
|
445
|
+
} else {
|
|
446
|
+
// Token amount in atomic units
|
|
447
|
+
maxAmountRequired = price.amount;
|
|
448
|
+
asset = price.asset;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
maxAmountRequired,
|
|
453
|
+
asset,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function getDefaultAsset(
|
|
458
|
+
network: FacilitatorNetwork,
|
|
459
|
+
facilitator: ReturnType<typeof facilitatorType>,
|
|
460
|
+
): Promise<ERC20TokenAmount["asset"] | undefined> {
|
|
461
|
+
const supportedAssets = await facilitator.supported();
|
|
462
|
+
const chainId = networkToChainId(network);
|
|
463
|
+
const matchingAsset = supportedAssets.kinds.find(
|
|
464
|
+
(supported) => supported.network === `eip155:${chainId}`,
|
|
465
|
+
);
|
|
466
|
+
const assetConfig = matchingAsset?.extra
|
|
467
|
+
?.defaultAsset as ERC20TokenAmount["asset"];
|
|
468
|
+
return assetConfig;
|
|
469
|
+
}
|