thirdweb 5.107.1 → 5.108.0
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 +8 -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/x402/common.js +173 -0
- package/dist/cjs/x402/common.js.map +1 -0
- 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/settle-payment.js +174 -0
- package/dist/cjs/x402/settle-payment.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/types.js +5 -0
- package/dist/cjs/x402/types.js.map +1 -0
- package/dist/cjs/x402/verify-payment.js +121 -0
- package/dist/cjs/x402/verify-payment.js.map +1 -0
- package/dist/esm/exports/x402.js +3 -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/x402/common.js +170 -0
- package/dist/esm/x402/common.js.map +1 -0
- 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/settle-payment.js +171 -0
- package/dist/esm/x402/settle-payment.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/types.js +2 -0
- package/dist/esm/x402/types.js.map +1 -0
- package/dist/esm/x402/verify-payment.js +118 -0
- package/dist/esm/x402/verify-payment.js.map +1 -0
- package/dist/types/exports/x402.d.ts +4 -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/x402/common.d.ts +16 -0
- package/dist/types/x402/common.d.ts.map +1 -0
- 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/settle-payment.d.ts +117 -0
- package/dist/types/x402/settle-payment.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/types.d.ts +71 -0
- package/dist/types/x402/types.d.ts.map +1 -0
- package/dist/types/x402/verify-payment.d.ts +69 -0
- package/dist/types/x402/verify-payment.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/exports/x402.ts +8 -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/common.ts +242 -0
- 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/settle-payment.ts +186 -0
- package/src/x402/sign.ts +206 -0
- package/src/x402/types.ts +90 -0
- package/src/x402/verify-payment.ts +133 -0
|
@@ -289,15 +289,9 @@ const DEFAULT_TOKENS = {
|
|
|
289
289
|
symbol: "USDC",
|
|
290
290
|
},
|
|
291
291
|
],
|
|
292
|
-
"
|
|
292
|
+
"421614": [
|
|
293
293
|
{
|
|
294
|
-
address: "
|
|
295
|
-
icon: wrappedEthIcon,
|
|
296
|
-
name: "Wrapped Ether",
|
|
297
|
-
symbol: "WETH",
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
address: "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63",
|
|
294
|
+
address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
|
|
301
295
|
icon: usdcIcon,
|
|
302
296
|
name: "USD Coin",
|
|
303
297
|
symbol: "USDC",
|
|
@@ -82,17 +82,17 @@ export interface BridgeOrchestratorProps {
|
|
|
82
82
|
/**
|
|
83
83
|
* Called when the flow is completed successfully
|
|
84
84
|
*/
|
|
85
|
-
onComplete: () => void;
|
|
85
|
+
onComplete: (quote: BridgePrepareResult) => void;
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Called when the flow encounters an error
|
|
89
89
|
*/
|
|
90
|
-
onError: (error: Error) => void;
|
|
90
|
+
onError: (error: Error, quote: BridgePrepareResult | undefined) => void;
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Called when the user cancels the flow
|
|
94
94
|
*/
|
|
95
|
-
onCancel: () => void;
|
|
95
|
+
onCancel: (quote: BridgePrepareResult | undefined) => void;
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Connect options for wallet connection
|
|
@@ -189,19 +189,22 @@ export function BridgeOrchestrator({
|
|
|
189
189
|
}, [send, uiOptions.mode]);
|
|
190
190
|
|
|
191
191
|
// Handle post-buy transaction completion
|
|
192
|
-
const handlePostBuyTransactionComplete = useCallback(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
const handlePostBuyTransactionComplete = useCallback(
|
|
193
|
+
(quote: BridgePrepareResult) => {
|
|
194
|
+
onComplete?.(quote);
|
|
195
|
+
send({ type: "RESET" });
|
|
196
|
+
},
|
|
197
|
+
[onComplete, send],
|
|
198
|
+
);
|
|
196
199
|
|
|
197
200
|
// Handle errors
|
|
198
201
|
const handleError = useCallback(
|
|
199
202
|
(error: Error) => {
|
|
200
203
|
console.error(error);
|
|
201
|
-
onError?.(error);
|
|
204
|
+
onError?.(error, state.context.quote);
|
|
202
205
|
send({ error, type: "ERROR_OCCURRED" });
|
|
203
206
|
},
|
|
204
|
-
[onError, send],
|
|
207
|
+
[onError, send, state.context.quote],
|
|
205
208
|
);
|
|
206
209
|
|
|
207
210
|
// Handle payment method selection
|
|
@@ -227,10 +230,13 @@ export function BridgeOrchestrator({
|
|
|
227
230
|
|
|
228
231
|
// Handle execution complete
|
|
229
232
|
const handleExecutionComplete = useCallback(
|
|
230
|
-
(
|
|
233
|
+
(
|
|
234
|
+
completedStatuses: CompletedStatusResult[],
|
|
235
|
+
quote: BridgePrepareResult,
|
|
236
|
+
) => {
|
|
231
237
|
send({ completedStatuses, type: "EXECUTION_COMPLETE" });
|
|
232
238
|
if (uiOptions.mode !== "transaction") {
|
|
233
|
-
onComplete?.();
|
|
239
|
+
onComplete?.(quote);
|
|
234
240
|
}
|
|
235
241
|
},
|
|
236
242
|
[send, onComplete, uiOptions.mode],
|
|
@@ -241,6 +247,8 @@ export function BridgeOrchestrator({
|
|
|
241
247
|
send({ type: "RETRY" });
|
|
242
248
|
}, [send]);
|
|
243
249
|
|
|
250
|
+
const quote = state.context.quote;
|
|
251
|
+
|
|
244
252
|
// Handle requirements resolved from FundWallet and DirectPayment
|
|
245
253
|
const handleRequirementsResolved = useCallback(
|
|
246
254
|
(amount: string, token: TokenWithPrices, receiverAddress: Address) => {
|
|
@@ -263,7 +271,7 @@ export function BridgeOrchestrator({
|
|
|
263
271
|
error={state.context.currentError}
|
|
264
272
|
onCancel={() => {
|
|
265
273
|
send({ type: "RESET" });
|
|
266
|
-
onCancel?.();
|
|
274
|
+
onCancel?.(quote);
|
|
267
275
|
}}
|
|
268
276
|
onRetry={handleRetry}
|
|
269
277
|
/>
|
|
@@ -369,31 +377,33 @@ export function BridgeOrchestrator({
|
|
|
369
377
|
/>
|
|
370
378
|
)}
|
|
371
379
|
|
|
372
|
-
{state.value === "execute" &&
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
380
|
+
{state.value === "execute" && quote && state.context.request && (
|
|
381
|
+
<StepRunner
|
|
382
|
+
autoStart={true}
|
|
383
|
+
client={client}
|
|
384
|
+
onBack={() => {
|
|
385
|
+
send({ type: "BACK" });
|
|
386
|
+
}}
|
|
387
|
+
onCancel={() => {
|
|
388
|
+
onCancel(quote);
|
|
389
|
+
}}
|
|
390
|
+
onComplete={(completedStatuses) => {
|
|
391
|
+
handleExecutionComplete(completedStatuses, quote);
|
|
392
|
+
}}
|
|
393
|
+
request={state.context.request}
|
|
394
|
+
wallet={state.context.selectedPaymentMethod?.payerWallet}
|
|
395
|
+
windowAdapter={webWindowAdapter}
|
|
396
|
+
/>
|
|
397
|
+
)}
|
|
388
398
|
|
|
389
399
|
{state.value === "success" &&
|
|
390
|
-
|
|
400
|
+
quote &&
|
|
391
401
|
state.context.completedStatuses && (
|
|
392
402
|
<SuccessScreen
|
|
393
403
|
client={client}
|
|
394
404
|
completedStatuses={state.context.completedStatuses}
|
|
395
405
|
onDone={handleDoneOrContinueClick}
|
|
396
|
-
preparedQuote={
|
|
406
|
+
preparedQuote={quote}
|
|
397
407
|
uiOptions={uiOptions}
|
|
398
408
|
windowAdapter={webWindowAdapter}
|
|
399
409
|
hasPaymentId={!!paymentLinkId}
|
|
@@ -402,9 +412,10 @@ export function BridgeOrchestrator({
|
|
|
402
412
|
|
|
403
413
|
{state.value === "post-buy-transaction" &&
|
|
404
414
|
uiOptions.mode === "transaction" &&
|
|
415
|
+
quote &&
|
|
405
416
|
uiOptions.transaction && (
|
|
406
417
|
<ExecutingTxScreen
|
|
407
|
-
closeModal={handlePostBuyTransactionComplete}
|
|
418
|
+
closeModal={() => handlePostBuyTransactionComplete(quote)}
|
|
408
419
|
onTxSent={() => {
|
|
409
420
|
// Do nothing
|
|
410
421
|
}}
|
|
@@ -23,6 +23,7 @@ import { CustomThemeProvider } from "../../../core/design-system/CustomThemeProv
|
|
|
23
23
|
import type { Theme } from "../../../core/design-system/index.js";
|
|
24
24
|
import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js";
|
|
25
25
|
import type { ConnectButton_connectModalOptions } from "../../../core/hooks/connection/ConnectButtonProps.js";
|
|
26
|
+
import type { BridgePrepareResult } from "../../../core/hooks/useBridgePrepare.js";
|
|
26
27
|
import type { SupportedTokens } from "../../../core/utils/defaultTokens.js";
|
|
27
28
|
import { useConnectLocale } from "../ConnectWallet/locale/getConnectLocale.js";
|
|
28
29
|
import { EmbedContainer } from "../ConnectWallet/Modal/ConnectEmbed.js";
|
|
@@ -32,6 +33,11 @@ import type { LocaleId } from "../types.js";
|
|
|
32
33
|
import { BridgeOrchestrator, type UIOptions } from "./BridgeOrchestrator.js";
|
|
33
34
|
import { UnsupportedTokenScreen } from "./UnsupportedTokenScreen.js";
|
|
34
35
|
|
|
36
|
+
type BuyOrOnrampPrepareResult = Extract<
|
|
37
|
+
BridgePrepareResult,
|
|
38
|
+
{ type: "buy" | "onramp" }
|
|
39
|
+
>;
|
|
40
|
+
|
|
35
41
|
export type BuyWidgetProps = {
|
|
36
42
|
/**
|
|
37
43
|
* Customize the supported tokens that users can pay with.
|
|
@@ -155,17 +161,17 @@ export type BuyWidgetProps = {
|
|
|
155
161
|
/**
|
|
156
162
|
* Callback triggered when the purchase is successful.
|
|
157
163
|
*/
|
|
158
|
-
onSuccess?: () => void;
|
|
164
|
+
onSuccess?: (quote: BuyOrOnrampPrepareResult) => void;
|
|
159
165
|
|
|
160
166
|
/**
|
|
161
167
|
* Callback triggered when the purchase encounters an error.
|
|
162
168
|
*/
|
|
163
|
-
onError?: (error: Error) => void;
|
|
169
|
+
onError?: (error: Error, quote: BuyOrOnrampPrepareResult | undefined) => void;
|
|
164
170
|
|
|
165
171
|
/**
|
|
166
172
|
* Callback triggered when the user cancels the purchase.
|
|
167
173
|
*/
|
|
168
|
-
onCancel?: () => void;
|
|
174
|
+
onCancel?: (quote: BuyOrOnrampPrepareResult | undefined) => void;
|
|
169
175
|
|
|
170
176
|
/**
|
|
171
177
|
* @hidden
|
|
@@ -447,14 +453,23 @@ export function BuyWidget(props: BuyWidgetProps) {
|
|
|
447
453
|
client={props.client}
|
|
448
454
|
connectLocale={localeQuery.data}
|
|
449
455
|
connectOptions={props.connectOptions}
|
|
450
|
-
onCancel={() => {
|
|
451
|
-
|
|
456
|
+
onCancel={(quote) => {
|
|
457
|
+
// type guard
|
|
458
|
+
if (quote?.type === "buy" || quote?.type === "onramp") {
|
|
459
|
+
props.onCancel?.(quote);
|
|
460
|
+
}
|
|
452
461
|
}}
|
|
453
|
-
onComplete={() => {
|
|
454
|
-
|
|
462
|
+
onComplete={(quote) => {
|
|
463
|
+
// type guard
|
|
464
|
+
if (quote?.type === "buy" || quote?.type === "onramp") {
|
|
465
|
+
props.onSuccess?.(quote);
|
|
466
|
+
}
|
|
455
467
|
}}
|
|
456
|
-
onError={(err: Error) => {
|
|
457
|
-
|
|
468
|
+
onError={(err: Error, quote) => {
|
|
469
|
+
// type guard
|
|
470
|
+
if (quote?.type === "buy" || quote?.type === "onramp") {
|
|
471
|
+
props.onError?.(err, quote);
|
|
472
|
+
}
|
|
458
473
|
}}
|
|
459
474
|
paymentLinkId={props.paymentLinkId}
|
|
460
475
|
paymentMethods={props.paymentMethods}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "5.
|
|
1
|
+
export const version = "5.108.0";
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ExactEvmPayload } from "x402/types";
|
|
2
|
+
import {
|
|
3
|
+
type RequestedPaymentPayload,
|
|
4
|
+
RequestedPaymentPayloadSchema,
|
|
5
|
+
} from "./schemas.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Encodes a payment payload into a base64 string, ensuring bigint values are properly stringified
|
|
9
|
+
*
|
|
10
|
+
* @param payment - The payment payload to encode
|
|
11
|
+
* @returns A base64 encoded string representation of the payment payload
|
|
12
|
+
*/
|
|
13
|
+
export function encodePayment(payment: RequestedPaymentPayload): string {
|
|
14
|
+
let safe: RequestedPaymentPayload;
|
|
15
|
+
|
|
16
|
+
// evm
|
|
17
|
+
const evmPayload = payment.payload as ExactEvmPayload;
|
|
18
|
+
safe = {
|
|
19
|
+
...payment,
|
|
20
|
+
payload: {
|
|
21
|
+
...evmPayload,
|
|
22
|
+
authorization: Object.fromEntries(
|
|
23
|
+
Object.entries(evmPayload.authorization).map(([key, value]) => [
|
|
24
|
+
key,
|
|
25
|
+
typeof value === "bigint" ? (value as bigint).toString() : value,
|
|
26
|
+
]),
|
|
27
|
+
) as ExactEvmPayload["authorization"],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return safeBase64Encode(JSON.stringify(safe));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decodes a base64 encoded payment string back into a PaymentPayload object
|
|
35
|
+
*
|
|
36
|
+
* @param payment - The base64 encoded payment string to decode
|
|
37
|
+
* @returns The decoded and validated PaymentPayload object
|
|
38
|
+
*/
|
|
39
|
+
export function decodePayment(payment: string): RequestedPaymentPayload {
|
|
40
|
+
const decoded = safeBase64Decode(payment);
|
|
41
|
+
const parsed = JSON.parse(decoded);
|
|
42
|
+
|
|
43
|
+
const obj: RequestedPaymentPayload = {
|
|
44
|
+
...parsed,
|
|
45
|
+
payload: parsed.payload as ExactEvmPayload,
|
|
46
|
+
};
|
|
47
|
+
const validated = RequestedPaymentPayloadSchema.parse(obj);
|
|
48
|
+
return validated;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encodes a string to base64 format
|
|
53
|
+
*
|
|
54
|
+
* @param data - The string to be encoded to base64
|
|
55
|
+
* @returns The base64 encoded string
|
|
56
|
+
*/
|
|
57
|
+
export function safeBase64Encode(data: string): string {
|
|
58
|
+
if (
|
|
59
|
+
typeof globalThis !== "undefined" &&
|
|
60
|
+
typeof globalThis.btoa === "function"
|
|
61
|
+
) {
|
|
62
|
+
return globalThis.btoa(data);
|
|
63
|
+
}
|
|
64
|
+
return Buffer.from(data).toString("base64");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decodes a base64 string back to its original format
|
|
69
|
+
*
|
|
70
|
+
* @param data - The base64 encoded string to be decoded
|
|
71
|
+
* @returns The decoded string in UTF-8 format
|
|
72
|
+
*/
|
|
73
|
+
function safeBase64Decode(data: string): string {
|
|
74
|
+
if (
|
|
75
|
+
typeof globalThis !== "undefined" &&
|
|
76
|
+
typeof globalThis.atob === "function"
|
|
77
|
+
) {
|
|
78
|
+
return globalThis.atob(data);
|
|
79
|
+
}
|
|
80
|
+
return Buffer.from(data, "base64").toString("utf-8");
|
|
81
|
+
}
|