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.
Files changed (78) hide show
  1. package/dist/cjs/exports/x402.js +6 -1
  2. package/dist/cjs/exports/x402.js.map +1 -1
  3. package/dist/cjs/react/core/utils/defaultTokens.js +2 -8
  4. package/dist/cjs/react/core/utils/defaultTokens.js.map +1 -1
  5. package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js +18 -14
  6. package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
  7. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js +15 -6
  8. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js.map +1 -1
  9. package/dist/cjs/version.js +1 -1
  10. package/dist/cjs/version.js.map +1 -1
  11. package/dist/cjs/x402/encode.js +71 -0
  12. package/dist/cjs/x402/encode.js.map +1 -0
  13. package/dist/cjs/x402/facilitator.js +83 -2
  14. package/dist/cjs/x402/facilitator.js.map +1 -1
  15. package/dist/cjs/x402/fetchWithPayment.js +7 -15
  16. package/dist/cjs/x402/fetchWithPayment.js.map +1 -1
  17. package/dist/cjs/x402/schemas.js +50 -0
  18. package/dist/cjs/x402/schemas.js.map +1 -0
  19. package/dist/cjs/x402/sign.js +148 -0
  20. package/dist/cjs/x402/sign.js.map +1 -0
  21. package/dist/cjs/x402/verify-payment.js +341 -0
  22. package/dist/cjs/x402/verify-payment.js.map +1 -0
  23. package/dist/esm/exports/x402.js +2 -0
  24. package/dist/esm/exports/x402.js.map +1 -1
  25. package/dist/esm/react/core/utils/defaultTokens.js +2 -8
  26. package/dist/esm/react/core/utils/defaultTokens.js.map +1 -1
  27. package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js +18 -14
  28. package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
  29. package/dist/esm/react/web/ui/Bridge/BuyWidget.js +15 -6
  30. package/dist/esm/react/web/ui/Bridge/BuyWidget.js.map +1 -1
  31. package/dist/esm/version.js +1 -1
  32. package/dist/esm/version.js.map +1 -1
  33. package/dist/esm/x402/encode.js +66 -0
  34. package/dist/esm/x402/encode.js.map +1 -0
  35. package/dist/esm/x402/facilitator.js +83 -2
  36. package/dist/esm/x402/facilitator.js.map +1 -1
  37. package/dist/esm/x402/fetchWithPayment.js +8 -16
  38. package/dist/esm/x402/fetchWithPayment.js.map +1 -1
  39. package/dist/esm/x402/schemas.js +46 -0
  40. package/dist/esm/x402/schemas.js.map +1 -0
  41. package/dist/esm/x402/sign.js +145 -0
  42. package/dist/esm/x402/sign.js.map +1 -0
  43. package/dist/esm/x402/verify-payment.js +338 -0
  44. package/dist/esm/x402/verify-payment.js.map +1 -0
  45. package/dist/types/exports/x402.d.ts +2 -0
  46. package/dist/types/exports/x402.d.ts.map +1 -1
  47. package/dist/types/react/core/utils/defaultTokens.d.ts +2 -7
  48. package/dist/types/react/core/utils/defaultTokens.d.ts.map +1 -1
  49. package/dist/types/react/web/ui/Bridge/BridgeOrchestrator.d.ts +4 -3
  50. package/dist/types/react/web/ui/Bridge/BridgeOrchestrator.d.ts.map +1 -1
  51. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts +7 -3
  52. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts.map +1 -1
  53. package/dist/types/version.d.ts +1 -1
  54. package/dist/types/version.d.ts.map +1 -1
  55. package/dist/types/x402/encode.d.ts +23 -0
  56. package/dist/types/x402/encode.d.ts.map +1 -0
  57. package/dist/types/x402/facilitator.d.ts +44 -3
  58. package/dist/types/x402/facilitator.d.ts.map +1 -1
  59. package/dist/types/x402/fetchWithPayment.d.ts +1 -1
  60. package/dist/types/x402/fetchWithPayment.d.ts.map +1 -1
  61. package/dist/types/x402/schemas.d.ts +164 -0
  62. package/dist/types/x402/schemas.d.ts.map +1 -0
  63. package/dist/types/x402/sign.d.ts +12 -0
  64. package/dist/types/x402/sign.d.ts.map +1 -0
  65. package/dist/types/x402/verify-payment.d.ts +158 -0
  66. package/dist/types/x402/verify-payment.d.ts.map +1 -0
  67. package/package.json +3 -3
  68. package/src/exports/x402.ts +6 -0
  69. package/src/react/core/utils/defaultTokens.ts +2 -8
  70. package/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +42 -31
  71. package/src/react/web/ui/Bridge/BuyWidget.tsx +24 -9
  72. package/src/version.ts +1 -1
  73. package/src/x402/encode.ts +81 -0
  74. package/src/x402/facilitator.ts +114 -6
  75. package/src/x402/fetchWithPayment.ts +13 -27
  76. package/src/x402/schemas.ts +76 -0
  77. package/src/x402/sign.ts +206 -0
  78. package/src/x402/verify-payment.ts +469 -0
@@ -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
+ }
@@ -1,5 +1,12 @@
1
- import type { FacilitatorConfig } from "x402/types";
1
+ import type { SupportedPaymentKindsResponse, VerifyResponse } from "x402/types";
2
2
  import type { ThirdwebClient } from "../client/client.js";
3
+ import { stringify } from "../utils/json.js";
4
+ import { withCache } from "../utils/promise/withCache.js";
5
+ import type {
6
+ FacilitatorSettleResponse,
7
+ RequestedPaymentPayload,
8
+ RequestedPaymentRequirements,
9
+ } from "./schemas.js";
3
10
 
4
11
  export type ThirdwebX402FacilitatorConfig = {
5
12
  client: ThirdwebClient;
@@ -12,7 +19,7 @@ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
12
19
 
13
20
  /**
14
21
  * Creates a facilitator for the x402 payment protocol.
15
- * Use this with any x402 middleware to enable settling transactions with your thirdweb server wallet.
22
+ * You can use this with `verifyPayment` or with any x402 middleware to enable settling transactions with your thirdweb server wallet.
16
23
  *
17
24
  * @param config - The configuration for the facilitator
18
25
  * @returns a x402 compatible FacilitatorConfig
@@ -48,9 +55,7 @@ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
48
55
  *
49
56
  * @bridge x402
50
57
  */
51
- export function facilitator(
52
- config: ThirdwebX402FacilitatorConfig,
53
- ): FacilitatorConfig {
58
+ export function facilitator(config: ThirdwebX402FacilitatorConfig) {
54
59
  const secretKey = config.client.secretKey;
55
60
  if (!secretKey) {
56
61
  throw new Error("Client secret key is required for the x402 facilitator");
@@ -61,7 +66,7 @@ export function facilitator(
61
66
  "Server wallet address is required for the x402 facilitator",
62
67
  );
63
68
  }
64
- return {
69
+ const facilitator = {
65
70
  url: (config.baseUrl ?? DEFAULT_BASE_URL) as `${string}://${string}`,
66
71
  createAuthHeaders: async () => {
67
72
  return {
@@ -83,5 +88,108 @@ export function facilitator(
83
88
  },
84
89
  };
85
90
  },
91
+ /**
92
+ * Verifies a payment payload with the facilitator service
93
+ *
94
+ * @param payload - The payment payload to verify
95
+ * @param paymentRequirements - The payment requirements to verify against
96
+ * @returns A promise that resolves to the verification response
97
+ */
98
+ async verify(
99
+ payload: RequestedPaymentPayload,
100
+ paymentRequirements: RequestedPaymentRequirements,
101
+ ): Promise<VerifyResponse> {
102
+ const url = config.baseUrl ?? DEFAULT_BASE_URL;
103
+
104
+ let headers = { "Content-Type": "application/json" };
105
+ const authHeaders = await facilitator.createAuthHeaders();
106
+ headers = { ...headers, ...authHeaders.verify };
107
+
108
+ const res = await fetch(`${url}/verify`, {
109
+ method: "POST",
110
+ headers,
111
+ body: stringify({
112
+ x402Version: payload.x402Version,
113
+ paymentPayload: payload,
114
+ paymentRequirements: paymentRequirements,
115
+ }),
116
+ });
117
+
118
+ if (res.status !== 200) {
119
+ const text = `${res.statusText} ${await res.text()}`;
120
+ throw new Error(`Failed to verify payment: ${res.status} ${text}`);
121
+ }
122
+
123
+ const data = await res.json();
124
+ return data as VerifyResponse;
125
+ },
126
+
127
+ /**
128
+ * Settles a payment with the facilitator service
129
+ *
130
+ * @param payload - The payment payload to settle
131
+ * @param paymentRequirements - The payment requirements for the settlement
132
+ * @returns A promise that resolves to the settlement response
133
+ */
134
+ async settle(
135
+ payload: RequestedPaymentPayload,
136
+ paymentRequirements: RequestedPaymentRequirements,
137
+ ): Promise<FacilitatorSettleResponse> {
138
+ const url = config.baseUrl ?? DEFAULT_BASE_URL;
139
+
140
+ let headers = { "Content-Type": "application/json" };
141
+ const authHeaders = await facilitator.createAuthHeaders();
142
+ headers = { ...headers, ...authHeaders.settle };
143
+
144
+ const res = await fetch(`${url}/settle`, {
145
+ method: "POST",
146
+ headers,
147
+ body: JSON.stringify({
148
+ x402Version: payload.x402Version,
149
+ paymentPayload: payload,
150
+ paymentRequirements: paymentRequirements,
151
+ }),
152
+ });
153
+
154
+ if (res.status !== 200) {
155
+ const text = `${res.statusText} ${await res.text()}`;
156
+ throw new Error(`Failed to settle payment: ${res.status} ${text}`);
157
+ }
158
+
159
+ const data = await res.json();
160
+ return data as FacilitatorSettleResponse;
161
+ },
162
+
163
+ /**
164
+ * Gets the supported payment kinds from the facilitator service.
165
+ *
166
+ * @returns A promise that resolves to the supported payment kinds
167
+ */
168
+ async supported(): Promise<SupportedPaymentKindsResponse> {
169
+ const url = config.baseUrl ?? DEFAULT_BASE_URL;
170
+ return withCache(
171
+ async () => {
172
+ let headers = { "Content-Type": "application/json" };
173
+ const authHeaders = await facilitator.createAuthHeaders();
174
+ headers = { ...headers, ...authHeaders.supported };
175
+ const res = await fetch(`${url}/supported`, { headers });
176
+
177
+ if (res.status !== 200) {
178
+ throw new Error(
179
+ `Failed to get supported payment kinds: ${res.statusText}`,
180
+ );
181
+ }
182
+
183
+ const data = await res.json();
184
+ return data as SupportedPaymentKindsResponse;
185
+ },
186
+ {
187
+ cacheKey: `supported-payment-kinds-${url}`,
188
+ cacheTime: 1000 * 60 * 60 * 24, // 24 hours
189
+ },
190
+ );
191
+ },
86
192
  };
193
+
194
+ return facilitator;
87
195
  }
@@ -1,15 +1,13 @@
1
- import { createPaymentHeader } from "x402/client";
2
- import {
3
- ChainIdToNetwork,
4
- EvmNetworkToChainId,
5
- type PaymentRequirements,
6
- PaymentRequirementsSchema,
7
- type Signer,
8
- } from "x402/types";
9
- import { viemAdapter } from "../adapters/viem.js";
1
+ import { ChainIdToNetwork } from "x402/types";
10
2
  import { getCachedChain } from "../chains/utils.js";
11
3
  import type { ThirdwebClient } from "../client/client.js";
12
4
  import type { Wallet } from "../wallets/interfaces/wallet.js";
5
+ import {
6
+ networkToChainId,
7
+ type RequestedPaymentRequirements,
8
+ RequestedPaymentRequirementsSchema,
9
+ } from "./schemas.js";
10
+ import { createPaymentHeader } from "./sign.js";
13
11
 
14
12
  /**
15
13
  * Enables the payment of APIs using the x402 payment protocol.
@@ -52,7 +50,7 @@ import type { Wallet } from "../wallets/interfaces/wallet.js";
52
50
  */
53
51
  export function wrapFetchWithPayment(
54
52
  fetch: typeof globalThis.fetch,
55
- client: ThirdwebClient,
53
+ _client: ThirdwebClient,
56
54
  wallet: Wallet,
57
55
  maxValue: bigint = BigInt(1 * 10 ** 6), // Default to 1 USDC
58
56
  ) {
@@ -68,7 +66,7 @@ export function wrapFetchWithPayment(
68
66
  accepts: unknown[];
69
67
  };
70
68
  const parsedPaymentRequirements = accepts
71
- .map((x) => PaymentRequirementsSchema.parse(x))
69
+ .map((x) => RequestedPaymentRequirementsSchema.parse(x))
72
70
  .filter((x) => x.scheme === "exact"); // TODO (402): accept other schemes
73
71
 
74
72
  const account = wallet.getAccount();
@@ -89,16 +87,10 @@ export function wrapFetchWithPayment(
89
87
  throw new Error("Payment amount exceeds maximum allowed");
90
88
  }
91
89
 
92
- const paymentChainId = EvmNetworkToChainId.get(
90
+ const paymentChainId = networkToChainId(
93
91
  selectedPaymentRequirements.network,
94
92
  );
95
93
 
96
- if (!paymentChainId) {
97
- throw new Error(
98
- `No chain found for the selected payment requirement: ${selectedPaymentRequirements.network}`,
99
- );
100
- }
101
-
102
94
  // switch to the payment chain if it's not the current chain
103
95
  if (paymentChainId !== chain.id) {
104
96
  await wallet.switchChain(getCachedChain(paymentChainId));
@@ -108,14 +100,8 @@ export function wrapFetchWithPayment(
108
100
  }
109
101
  }
110
102
 
111
- const walletClient = viemAdapter.wallet.toViem({
112
- wallet: wallet,
113
- chain,
114
- client,
115
- }) as Signer;
116
-
117
103
  const paymentHeader = await createPaymentHeader(
118
- walletClient,
104
+ account,
119
105
  x402Version,
120
106
  selectedPaymentRequirements,
121
107
  );
@@ -142,7 +128,7 @@ export function wrapFetchWithPayment(
142
128
  }
143
129
 
144
130
  function defaultPaymentRequirementsSelector(
145
- paymentRequirements: PaymentRequirements[],
131
+ paymentRequirements: RequestedPaymentRequirements[],
146
132
  chainId: number,
147
133
  scheme: "exact",
148
134
  ) {
@@ -151,7 +137,7 @@ function defaultPaymentRequirementsSelector(
151
137
  "No valid payment requirements found in server 402 response",
152
138
  );
153
139
  }
154
- const currentWalletNetwork = ChainIdToNetwork[chainId];
140
+ const currentWalletNetwork = ChainIdToNetwork[chainId] || `eip155:${chainId}`;
155
141
  // find the payment requirements matching the connected wallet chain
156
142
  const matchingPaymentRequirements = paymentRequirements.find(
157
143
  (x) => x.network === currentWalletNetwork && x.scheme === scheme,
@@ -0,0 +1,76 @@
1
+ import {
2
+ EvmNetworkToChainId,
3
+ type ExactEvmPayload,
4
+ type Network,
5
+ PaymentPayloadSchema,
6
+ PaymentRequirementsSchema,
7
+ SettleResponseSchema,
8
+ } from "x402/types";
9
+ import { z } from "zod";
10
+
11
+ const FacilitatorNetworkSchema = z.union([
12
+ z.literal("base-sepolia"),
13
+ z.literal("base"),
14
+ z.literal("avalanche-fuji"),
15
+ z.literal("avalanche"),
16
+ z.literal("iotex"),
17
+ z.literal("solana-devnet"),
18
+ z.literal("solana"),
19
+ z.literal("sei"),
20
+ z.literal("sei-testnet"),
21
+ z.string().refine((value) => value.startsWith("eip155:"), {
22
+ message: "Invalid network",
23
+ }),
24
+ ]);
25
+
26
+ export type FacilitatorNetwork = z.infer<typeof FacilitatorNetworkSchema>;
27
+
28
+ export const RequestedPaymentPayloadSchema = PaymentPayloadSchema.extend({
29
+ network: FacilitatorNetworkSchema,
30
+ });
31
+
32
+ export type RequestedPaymentPayload = z.infer<
33
+ typeof RequestedPaymentPayloadSchema
34
+ >;
35
+ export type UnsignedPaymentPayload = Omit<
36
+ RequestedPaymentPayload,
37
+ "payload"
38
+ > & {
39
+ payload: Omit<ExactEvmPayload, "signature"> & { signature: undefined };
40
+ };
41
+
42
+ export const RequestedPaymentRequirementsSchema =
43
+ PaymentRequirementsSchema.extend({
44
+ network: FacilitatorNetworkSchema,
45
+ });
46
+
47
+ export type RequestedPaymentRequirements = z.infer<
48
+ typeof RequestedPaymentRequirementsSchema
49
+ >;
50
+
51
+ const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({
52
+ network: FacilitatorNetworkSchema,
53
+ });
54
+ export type FacilitatorSettleResponse = z.infer<
55
+ typeof FacilitatorSettleResponseSchema
56
+ >;
57
+
58
+ export function networkToChainId(network: string): number {
59
+ if (network.startsWith("eip155:")) {
60
+ const chainId = parseInt(network.split(":")[1] ?? "0");
61
+ if (!Number.isNaN(chainId) && chainId > 0) {
62
+ return chainId;
63
+ } else {
64
+ throw new Error(`Invalid network: ${network}`);
65
+ }
66
+ }
67
+ const mappedChainId = EvmNetworkToChainId.get(network as Network);
68
+ if (!mappedChainId) {
69
+ throw new Error(`Invalid network: ${network}`);
70
+ }
71
+ // TODO (402): support solana networks
72
+ if (mappedChainId === 101 || mappedChainId === 103) {
73
+ throw new Error("Solana networks not supported yet.");
74
+ }
75
+ return mappedChainId;
76
+ }
@@ -0,0 +1,206 @@
1
+ import type { ExactEvmPayloadAuthorization } from "x402/types";
2
+ import { type Address, getAddress } from "../utils/address.js";
3
+ import { type Hex, toHex } from "../utils/encoding/hex.js";
4
+ import type { Account } from "../wallets/interfaces/wallet.js";
5
+ import { encodePayment } from "./encode.js";
6
+ import {
7
+ networkToChainId,
8
+ type RequestedPaymentPayload,
9
+ type RequestedPaymentRequirements,
10
+ type UnsignedPaymentPayload,
11
+ } from "./schemas.js";
12
+
13
+ /**
14
+ * Prepares an unsigned payment header with the given sender address and payment requirements.
15
+ *
16
+ * @param from - The sender's address from which the payment will be made
17
+ * @param x402Version - The version of the X402 protocol to use
18
+ * @param paymentRequirements - The payment requirements containing scheme and network information
19
+ * @returns An unsigned payment payload containing authorization details
20
+ */
21
+ function preparePaymentHeader(
22
+ from: Address,
23
+ x402Version: number,
24
+ paymentRequirements: RequestedPaymentRequirements,
25
+ ): UnsignedPaymentPayload {
26
+ const nonce = createNonce();
27
+
28
+ const validAfter = BigInt(
29
+ Math.floor(Date.now() / 1000) - 600, // 10 minutes before
30
+ ).toString();
31
+ const validBefore = BigInt(
32
+ Math.floor(Date.now() / 1000 + paymentRequirements.maxTimeoutSeconds),
33
+ ).toString();
34
+
35
+ return {
36
+ x402Version,
37
+ scheme: paymentRequirements.scheme,
38
+ network: paymentRequirements.network,
39
+ payload: {
40
+ signature: undefined,
41
+ authorization: {
42
+ from,
43
+ to: paymentRequirements.payTo as Address,
44
+ value: paymentRequirements.maxAmountRequired,
45
+ validAfter: validAfter.toString(),
46
+ validBefore: validBefore.toString(),
47
+ nonce,
48
+ },
49
+ },
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Signs a payment header using the provided client and payment requirements.
55
+ *
56
+ * @param client - The signer wallet instance used to sign the payment header
57
+ * @param paymentRequirements - The payment requirements containing scheme and network information
58
+ * @param unsignedPaymentHeader - The unsigned payment payload to be signed
59
+ * @returns A promise that resolves to the signed payment payload
60
+ */
61
+ async function signPaymentHeader(
62
+ account: Account,
63
+ paymentRequirements: RequestedPaymentRequirements,
64
+ unsignedPaymentHeader: UnsignedPaymentPayload,
65
+ ): Promise<RequestedPaymentPayload> {
66
+ const { signature } = await signAuthorization(
67
+ account,
68
+ unsignedPaymentHeader.payload.authorization,
69
+ paymentRequirements,
70
+ );
71
+
72
+ return {
73
+ ...unsignedPaymentHeader,
74
+ payload: {
75
+ ...unsignedPaymentHeader.payload,
76
+ signature,
77
+ },
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Creates a complete payment payload by preparing and signing a payment header.
83
+ *
84
+ * @param client - The signer wallet instance used to create and sign the payment
85
+ * @param x402Version - The version of the X402 protocol to use
86
+ * @param paymentRequirements - The payment requirements containing scheme and network information
87
+ * @returns A promise that resolves to the complete signed payment payload
88
+ */
89
+ async function createPayment(
90
+ account: Account,
91
+ x402Version: number,
92
+ paymentRequirements: RequestedPaymentRequirements,
93
+ ): Promise<RequestedPaymentPayload> {
94
+ const from = getAddress(account.address);
95
+ const unsignedPaymentHeader = preparePaymentHeader(
96
+ from,
97
+ x402Version,
98
+ paymentRequirements,
99
+ );
100
+ return signPaymentHeader(account, paymentRequirements, unsignedPaymentHeader);
101
+ }
102
+
103
+ /**
104
+ * Creates and encodes a payment header for the given client and payment requirements.
105
+ *
106
+ * @param client - The signer wallet instance used to create the payment header
107
+ * @param x402Version - The version of the X402 protocol to use
108
+ * @param paymentRequirements - The payment requirements containing scheme and network information
109
+ * @returns A promise that resolves to the encoded payment header string
110
+ */
111
+ export async function createPaymentHeader(
112
+ account: Account,
113
+ x402Version: number,
114
+ paymentRequirements: RequestedPaymentRequirements,
115
+ ): Promise<string> {
116
+ const payment = await createPayment(
117
+ account,
118
+ x402Version,
119
+ paymentRequirements,
120
+ );
121
+ return encodePayment(payment);
122
+ }
123
+
124
+ /**
125
+ * Signs an EIP-3009 authorization for USDC transfer
126
+ *
127
+ * @param walletClient - The wallet client that will sign the authorization
128
+ * @param params - The authorization parameters containing transfer details
129
+ * @param params.from - The address tokens will be transferred from
130
+ * @param params.to - The address tokens will be transferred to
131
+ * @param params.value - The amount of USDC tokens to transfer (in base units)
132
+ * @param params.validAfter - Unix timestamp after which the authorization becomes valid
133
+ * @param params.validBefore - Unix timestamp before which the authorization is valid
134
+ * @param params.nonce - Random 32-byte nonce to prevent replay attacks
135
+ * @param paymentRequirements - The payment requirements containing asset and network information
136
+ * @param paymentRequirements.asset - The address of the USDC contract
137
+ * @param paymentRequirements.network - The network where the USDC contract exists
138
+ * @param paymentRequirements.extra - The extra information containing the name and version of the ERC20 contract
139
+ * @returns The signature for the authorization
140
+ */
141
+ async function signAuthorization(
142
+ account: Account,
143
+ {
144
+ from,
145
+ to,
146
+ value,
147
+ validAfter,
148
+ validBefore,
149
+ nonce,
150
+ }: ExactEvmPayloadAuthorization,
151
+ { asset, network, extra }: RequestedPaymentRequirements,
152
+ ): Promise<{ signature: Hex }> {
153
+ const chainId = networkToChainId(network);
154
+ const name = extra?.name;
155
+ const version = extra?.version;
156
+
157
+ // TODO (402): detect permit vs transfer on asset contract
158
+ const data = {
159
+ types: {
160
+ TransferWithAuthorization: [
161
+ { name: "from", type: "address" },
162
+ { name: "to", type: "address" },
163
+ { name: "value", type: "uint256" },
164
+ { name: "validAfter", type: "uint256" },
165
+ { name: "validBefore", type: "uint256" },
166
+ { name: "nonce", type: "bytes32" },
167
+ ],
168
+ },
169
+ domain: {
170
+ name,
171
+ version,
172
+ chainId,
173
+ verifyingContract: getAddress(asset),
174
+ },
175
+ primaryType: "TransferWithAuthorization" as const,
176
+ message: {
177
+ from: getAddress(from),
178
+ to: getAddress(to),
179
+ value,
180
+ validAfter,
181
+ validBefore,
182
+ nonce: nonce,
183
+ },
184
+ };
185
+
186
+ const signature = await account.signTypedData(data);
187
+ return {
188
+ signature,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Generates a random 32-byte nonce for use in authorization signatures
194
+ *
195
+ * @returns A random 32-byte nonce as a hex string
196
+ */
197
+ function createNonce(): Hex {
198
+ const cryptoObj =
199
+ typeof globalThis.crypto !== "undefined" &&
200
+ typeof globalThis.crypto.getRandomValues === "function"
201
+ ? globalThis.crypto
202
+ : // Dynamic require is needed to support node.js
203
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
204
+ require("crypto").webcrypto;
205
+ return toHex(cryptoObj.getRandomValues(new Uint8Array(32)));
206
+ }