@x402/fetch 0.0.1 → 2.1.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/README.md +196 -1
- package/dist/cjs/index.d.ts +52 -0
- package/dist/cjs/index.js +97 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +52 -0
- package/dist/esm/index.mjs +68 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +56 -8
- package/index.js +0 -3
package/README.md
CHANGED
|
@@ -1 +1,196 @@
|
|
|
1
|
-
#
|
|
1
|
+
# x402-fetch
|
|
2
|
+
|
|
3
|
+
A utility package that extends the native `fetch` API to automatically handle 402 Payment Required responses using the x402 payment protocol v2. This package enables seamless integration of payment functionality into your applications when making HTTP requests.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install @x402/fetch
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
|
|
15
|
+
import { ExactEvmScheme } from "@x402/evm";
|
|
16
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
17
|
+
|
|
18
|
+
// Create an account
|
|
19
|
+
const account = privateKeyToAccount("0xYourPrivateKey");
|
|
20
|
+
|
|
21
|
+
// Wrap the fetch function with payment handling
|
|
22
|
+
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
23
|
+
schemes: [
|
|
24
|
+
{
|
|
25
|
+
network: "eip155:8453", // Base Sepolia
|
|
26
|
+
client: new ExactEvmScheme(account),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Make a request that may require payment
|
|
32
|
+
const response = await fetchWithPayment("https://api.example.com/paid-endpoint", {
|
|
33
|
+
method: "GET",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
### `wrapFetchWithPayment(fetch, client)`
|
|
42
|
+
|
|
43
|
+
Wraps the native fetch API to handle 402 Payment Required responses automatically.
|
|
44
|
+
|
|
45
|
+
#### Parameters
|
|
46
|
+
|
|
47
|
+
- `fetch`: The fetch function to wrap (typically `globalThis.fetch`)
|
|
48
|
+
- `client`: An x402Client instance with registered payment schemes
|
|
49
|
+
|
|
50
|
+
### `wrapFetchWithPaymentFromConfig(fetch, config)`
|
|
51
|
+
|
|
52
|
+
Convenience wrapper that creates an x402Client from a configuration object.
|
|
53
|
+
|
|
54
|
+
#### Parameters
|
|
55
|
+
|
|
56
|
+
- `fetch`: The fetch function to wrap (typically `globalThis.fetch`)
|
|
57
|
+
- `config`: Configuration object with the following properties:
|
|
58
|
+
- `schemes`: Array of scheme registrations, each containing:
|
|
59
|
+
- `network`: Network identifier (e.g., 'eip155:8453', 'solana:mainnet', 'eip155:*' for wildcards)
|
|
60
|
+
- `client`: The scheme client implementation (e.g., `ExactEvmScheme`, `ExactSvmScheme`)
|
|
61
|
+
- `x402Version`: Optional protocol version (defaults to 2, set to 1 for legacy support)
|
|
62
|
+
- `paymentRequirementsSelector`: Optional function to select payment requirements from multiple options
|
|
63
|
+
|
|
64
|
+
#### Returns
|
|
65
|
+
|
|
66
|
+
A wrapped fetch function that automatically handles 402 responses by:
|
|
67
|
+
1. Making the initial request
|
|
68
|
+
2. If a 402 response is received, parsing the payment requirements
|
|
69
|
+
3. Creating a payment header using the configured scheme client
|
|
70
|
+
4. Retrying the request with the payment header
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
### Basic Usage with EVM
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { config } from "dotenv";
|
|
78
|
+
import { wrapFetchWithPaymentFromConfig, decodePaymentResponseHeader } from "@x402/fetch";
|
|
79
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
80
|
+
import { ExactEvmScheme } from "@x402/evm";
|
|
81
|
+
|
|
82
|
+
config();
|
|
83
|
+
|
|
84
|
+
const { EVM_PRIVATE_KEY, API_URL } = process.env;
|
|
85
|
+
|
|
86
|
+
const account = privateKeyToAccount(EVM_PRIVATE_KEY as `0x${string}`);
|
|
87
|
+
|
|
88
|
+
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
89
|
+
schemes: [
|
|
90
|
+
{
|
|
91
|
+
network: "eip155:*", // Support all EVM chains
|
|
92
|
+
client: new ExactEvmScheme(account),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Make a request to a paid API endpoint
|
|
98
|
+
fetchWithPayment(API_URL, {
|
|
99
|
+
method: "GET",
|
|
100
|
+
})
|
|
101
|
+
.then(async response => {
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
|
|
104
|
+
// Optionally decode the payment response header
|
|
105
|
+
const paymentResponse = response.headers.get("PAYMENT-RESPONSE");
|
|
106
|
+
if (paymentResponse) {
|
|
107
|
+
const decoded = decodePaymentResponseHeader(paymentResponse);
|
|
108
|
+
console.log("Payment details:", decoded);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("Response data:", data);
|
|
112
|
+
})
|
|
113
|
+
.catch(error => {
|
|
114
|
+
console.error(error);
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Using Builder Pattern
|
|
119
|
+
|
|
120
|
+
For more control, you can use the builder pattern to register multiple schemes:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
|
|
124
|
+
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
125
|
+
import { ExactSvmScheme } from "@x402/svm/exact/client";
|
|
126
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
127
|
+
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
128
|
+
import { base58 } from "@scure/base";
|
|
129
|
+
|
|
130
|
+
// Create signers
|
|
131
|
+
const evmSigner = privateKeyToAccount("0xYourPrivateKey");
|
|
132
|
+
const svmSigner = await createKeyPairSignerFromBytes(base58.decode("YourSvmPrivateKey"));
|
|
133
|
+
|
|
134
|
+
// Build client with multiple schemes
|
|
135
|
+
const client = new x402Client()
|
|
136
|
+
.register("eip155:*", new ExactEvmScheme(evmSigner))
|
|
137
|
+
.register("solana:*", new ExactSvmScheme(svmSigner));
|
|
138
|
+
|
|
139
|
+
// Wrap fetch with the client
|
|
140
|
+
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Multi-Chain Support
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
|
|
147
|
+
import { ExactEvmScheme } from "@x402/evm";
|
|
148
|
+
import { ExactSvmScheme } from "@x402/svm";
|
|
149
|
+
|
|
150
|
+
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
151
|
+
schemes: [
|
|
152
|
+
// EVM chains
|
|
153
|
+
{
|
|
154
|
+
network: "eip155:8453", // Base Sepolia
|
|
155
|
+
client: new ExactEvmScheme(evmAccount),
|
|
156
|
+
},
|
|
157
|
+
// SVM chains
|
|
158
|
+
{
|
|
159
|
+
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", // Solana devnet
|
|
160
|
+
client: new ExactSvmScheme(svmSigner),
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Custom Payment Requirements Selector
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { wrapFetchWithPaymentFromConfig, type SelectPaymentRequirements } from "@x402/fetch";
|
|
170
|
+
import { ExactEvmScheme } from "@x402/evm";
|
|
171
|
+
|
|
172
|
+
// Custom selector that prefers the cheapest option
|
|
173
|
+
const selectCheapestOption: SelectPaymentRequirements = (version, accepts) => {
|
|
174
|
+
if (!accepts || accepts.length === 0) {
|
|
175
|
+
throw new Error("No payment options available");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Sort by value and return the cheapest
|
|
179
|
+
const sorted = [...accepts].sort((a, b) =>
|
|
180
|
+
BigInt(a.value) - BigInt(b.value)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return sorted[0];
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
187
|
+
schemes: [
|
|
188
|
+
{
|
|
189
|
+
network: "eip155:8453",
|
|
190
|
+
client: new ExactEvmScheme(account),
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
paymentRequirementsSelector: selectCheapestOption,
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { x402Client, x402HTTPClient, x402ClientConfig } from '@x402/core/client';
|
|
2
|
+
export { PaymentPolicy, SchemeRegistration, SelectPaymentRequirements, x402Client, x402ClientConfig, x402HTTPClient } from '@x402/core/client';
|
|
3
|
+
export { decodePaymentResponseHeader } from '@x402/core/http';
|
|
4
|
+
export { Network, PaymentPayload, PaymentRequired, PaymentRequirements, SchemeNetworkClient } from '@x402/core/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enables the payment of APIs using the x402 payment protocol v2.
|
|
8
|
+
*
|
|
9
|
+
* This function wraps the native fetch API to automatically handle 402 Payment Required responses
|
|
10
|
+
* by creating and sending payment headers. It will:
|
|
11
|
+
* 1. Make the initial request
|
|
12
|
+
* 2. If a 402 response is received, parse the payment requirements
|
|
13
|
+
* 3. Create a payment header using the configured x402HTTPClient
|
|
14
|
+
* 4. Retry the request with the payment header
|
|
15
|
+
*
|
|
16
|
+
* @param fetch - The fetch function to wrap (typically globalThis.fetch)
|
|
17
|
+
* @param client - Configured x402Client or x402HTTPClient instance for handling payments
|
|
18
|
+
* @returns A wrapped fetch function that handles 402 responses automatically
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
|
|
23
|
+
* import { ExactEvmScheme } from '@x402/evm';
|
|
24
|
+
* import { ExactSvmScheme } from '@x402/svm';
|
|
25
|
+
*
|
|
26
|
+
* const client = new x402Client()
|
|
27
|
+
* .register('eip155:8453', new ExactEvmScheme(evmSigner))
|
|
28
|
+
* .register('solana:mainnet', new ExactSvmScheme(svmSigner))
|
|
29
|
+
* .register('eip155:1', new ExactEvmScheme(evmSigner), 1); // v1 protocol
|
|
30
|
+
*
|
|
31
|
+
* const fetchWithPay = wrapFetchWithPayment(fetch, client);
|
|
32
|
+
*
|
|
33
|
+
* // Make a request that may require payment
|
|
34
|
+
* const response = await fetchWithPay('https://api.example.com/paid-endpoint');
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @throws {Error} If no schemes are provided
|
|
38
|
+
* @throws {Error} If the request configuration is missing
|
|
39
|
+
* @throws {Error} If a payment has already been attempted for this request
|
|
40
|
+
* @throws {Error} If there's an error creating the payment header
|
|
41
|
+
*/
|
|
42
|
+
declare function wrapFetchWithPayment(fetch: typeof globalThis.fetch, client: x402Client | x402HTTPClient): (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a payment-enabled fetch function from a configuration object.
|
|
45
|
+
*
|
|
46
|
+
* @param fetch - The fetch function to wrap (typically globalThis.fetch)
|
|
47
|
+
* @param config - Configuration options including scheme registrations and selectors
|
|
48
|
+
* @returns A wrapped fetch function that handles 402 responses automatically
|
|
49
|
+
*/
|
|
50
|
+
declare function wrapFetchWithPaymentFromConfig(fetch: typeof globalThis.fetch, config: x402ClientConfig): (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
51
|
+
|
|
52
|
+
export { wrapFetchWithPayment, wrapFetchWithPaymentFromConfig };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
decodePaymentResponseHeader: () => import_http.decodePaymentResponseHeader,
|
|
24
|
+
wrapFetchWithPayment: () => wrapFetchWithPayment,
|
|
25
|
+
wrapFetchWithPaymentFromConfig: () => wrapFetchWithPaymentFromConfig,
|
|
26
|
+
x402Client: () => import_client2.x402Client,
|
|
27
|
+
x402HTTPClient: () => import_client2.x402HTTPClient
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
var import_client = require("@x402/core/client");
|
|
31
|
+
var import_client2 = require("@x402/core/client");
|
|
32
|
+
var import_http = require("@x402/core/http");
|
|
33
|
+
function wrapFetchWithPayment(fetch, client) {
|
|
34
|
+
const httpClient = client instanceof import_client.x402HTTPClient ? client : new import_client.x402HTTPClient(client);
|
|
35
|
+
return async (input, init) => {
|
|
36
|
+
const response = await fetch(input, init);
|
|
37
|
+
if (response.status !== 402) {
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
let paymentRequired;
|
|
41
|
+
try {
|
|
42
|
+
const getHeader = (name) => response.headers.get(name);
|
|
43
|
+
let body;
|
|
44
|
+
try {
|
|
45
|
+
const responseText = await response.text();
|
|
46
|
+
if (responseText) {
|
|
47
|
+
body = JSON.parse(responseText);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
let paymentPayload;
|
|
58
|
+
try {
|
|
59
|
+
paymentPayload = await client.createPaymentPayload(paymentRequired);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Failed to create payment payload: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
66
|
+
if (!init) {
|
|
67
|
+
throw new Error("Missing fetch request configuration");
|
|
68
|
+
}
|
|
69
|
+
if (init.__is402Retry) {
|
|
70
|
+
throw new Error("Payment already attempted");
|
|
71
|
+
}
|
|
72
|
+
const newInit = {
|
|
73
|
+
...init,
|
|
74
|
+
headers: {
|
|
75
|
+
...init.headers || {},
|
|
76
|
+
...paymentHeaders,
|
|
77
|
+
"Access-Control-Expose-Headers": "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
|
|
78
|
+
},
|
|
79
|
+
__is402Retry: true
|
|
80
|
+
};
|
|
81
|
+
const secondResponse = await fetch(input, newInit);
|
|
82
|
+
return secondResponse;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function wrapFetchWithPaymentFromConfig(fetch, config) {
|
|
86
|
+
const client = import_client.x402Client.fromConfig(config);
|
|
87
|
+
return wrapFetchWithPayment(fetch, client);
|
|
88
|
+
}
|
|
89
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
90
|
+
0 && (module.exports = {
|
|
91
|
+
decodePaymentResponseHeader,
|
|
92
|
+
wrapFetchWithPayment,
|
|
93
|
+
wrapFetchWithPaymentFromConfig,
|
|
94
|
+
x402Client,
|
|
95
|
+
x402HTTPClient
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import { x402Client, x402ClientConfig, x402HTTPClient } from \"@x402/core/client\";\nimport { type PaymentRequired } from \"@x402/core/types\";\n\n/**\n * Enables the payment of APIs using the x402 payment protocol v2.\n *\n * This function wraps the native fetch API to automatically handle 402 Payment Required responses\n * by creating and sending payment headers. It will:\n * 1. Make the initial request\n * 2. If a 402 response is received, parse the payment requirements\n * 3. Create a payment header using the configured x402HTTPClient\n * 4. Retry the request with the payment header\n *\n * @param fetch - The fetch function to wrap (typically globalThis.fetch)\n * @param client - Configured x402Client or x402HTTPClient instance for handling payments\n * @returns A wrapped fetch function that handles 402 responses automatically\n *\n * @example\n * ```typescript\n * import { wrapFetchWithPayment, x402Client } from '@x402/fetch';\n * import { ExactEvmScheme } from '@x402/evm';\n * import { ExactSvmScheme } from '@x402/svm';\n *\n * const client = new x402Client()\n * .register('eip155:8453', new ExactEvmScheme(evmSigner))\n * .register('solana:mainnet', new ExactSvmScheme(svmSigner))\n * .register('eip155:1', new ExactEvmScheme(evmSigner), 1); // v1 protocol\n *\n * const fetchWithPay = wrapFetchWithPayment(fetch, client);\n *\n * // Make a request that may require payment\n * const response = await fetchWithPay('https://api.example.com/paid-endpoint');\n * ```\n *\n * @throws {Error} If no schemes are provided\n * @throws {Error} If the request configuration is missing\n * @throws {Error} If a payment has already been attempted for this request\n * @throws {Error} If there's an error creating the payment header\n */\nexport function wrapFetchWithPayment(\n fetch: typeof globalThis.fetch,\n client: x402Client | x402HTTPClient,\n) {\n const httpClient = client instanceof x402HTTPClient ? client : new x402HTTPClient(client);\n\n return async (input: RequestInfo, init?: RequestInit) => {\n const response = await fetch(input, init);\n\n if (response.status !== 402) {\n return response;\n }\n\n // Parse payment requirements from response\n let paymentRequired: PaymentRequired;\n try {\n // Create getHeader function for case-insensitive header lookup\n const getHeader = (name: string) => response.headers.get(name);\n\n // Try to get from headers first (v2), then from body (v1)\n let body: PaymentRequired | undefined;\n try {\n const responseText = await response.text();\n if (responseText) {\n body = JSON.parse(responseText) as PaymentRequired;\n }\n } catch {\n // Ignore JSON parse errors - might be header-only response\n }\n\n paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);\n } catch (error) {\n throw new Error(\n `Failed to parse payment requirements: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n // Create payment payload (copy extensions from PaymentRequired)\n let paymentPayload;\n try {\n paymentPayload = await client.createPaymentPayload(paymentRequired);\n } catch (error) {\n throw new Error(\n `Failed to create payment payload: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n // Encode payment header\n const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);\n\n // Ensure we have request init\n if (!init) {\n throw new Error(\"Missing fetch request configuration\");\n }\n\n // Check if this is already a retry to prevent infinite loops\n if ((init as { __is402Retry?: boolean }).__is402Retry) {\n throw new Error(\"Payment already attempted\");\n }\n\n // Create new request with payment header\n const newInit = {\n ...init,\n headers: {\n ...(init.headers || {}),\n ...paymentHeaders,\n \"Access-Control-Expose-Headers\": \"PAYMENT-RESPONSE,X-PAYMENT-RESPONSE\",\n },\n __is402Retry: true,\n };\n\n // Retry the request with payment\n const secondResponse = await fetch(input, newInit);\n return secondResponse;\n };\n}\n\n/**\n * Creates a payment-enabled fetch function from a configuration object.\n *\n * @param fetch - The fetch function to wrap (typically globalThis.fetch)\n * @param config - Configuration options including scheme registrations and selectors\n * @returns A wrapped fetch function that handles 402 responses automatically\n */\nexport function wrapFetchWithPaymentFromConfig(\n fetch: typeof globalThis.fetch,\n config: x402ClientConfig,\n) {\n const client = x402Client.fromConfig(config);\n return wrapFetchWithPayment(fetch, client);\n}\n\n// Re-export types and utilities for convenience\nexport { x402Client, x402HTTPClient } from \"@x402/core/client\";\nexport type {\n PaymentPolicy,\n SchemeRegistration,\n SelectPaymentRequirements,\n x402ClientConfig,\n} from \"@x402/core/client\";\nexport { decodePaymentResponseHeader } from \"@x402/core/http\";\nexport type {\n Network,\n PaymentPayload,\n PaymentRequired,\n PaymentRequirements,\n SchemeNetworkClient,\n} from \"@x402/core/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6D;AAoI7D,IAAAA,iBAA2C;AAO3C,kBAA4C;AApGrC,SAAS,qBACd,OACA,QACA;AACA,QAAM,aAAa,kBAAkB,+BAAiB,SAAS,IAAI,6BAAe,MAAM;AAExF,SAAO,OAAO,OAAoB,SAAuB;AACvD,UAAM,WAAW,MAAM,MAAM,OAAO,IAAI;AAExC,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,IACT;AAGA,QAAI;AACJ,QAAI;AAEF,YAAM,YAAY,CAAC,SAAiB,SAAS,QAAQ,IAAI,IAAI;AAG7D,UAAI;AACJ,UAAI;AACF,cAAM,eAAe,MAAM,SAAS,KAAK;AACzC,YAAI,cAAc;AAChB,iBAAO,KAAK,MAAM,YAAY;AAAA,QAChC;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,wBAAkB,WAAW,2BAA2B,WAAW,IAAI;AAAA,IACzE,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACnG;AAAA,IACF;AAGA,QAAI;AACJ,QAAI;AACF,uBAAiB,MAAM,OAAO,qBAAqB,eAAe;AAAA,IACpE,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MAC/F;AAAA,IACF;AAGA,UAAM,iBAAiB,WAAW,6BAA6B,cAAc;AAG7E,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAGA,QAAK,KAAoC,cAAc;AACrD,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAGA,UAAM,UAAU;AAAA,MACd,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAI,KAAK,WAAW,CAAC;AAAA,QACrB,GAAG;AAAA,QACH,iCAAiC;AAAA,MACnC;AAAA,MACA,cAAc;AAAA,IAChB;AAGA,UAAM,iBAAiB,MAAM,MAAM,OAAO,OAAO;AACjD,WAAO;AAAA,EACT;AACF;AASO,SAAS,+BACd,OACA,QACA;AACA,QAAM,SAAS,yBAAW,WAAW,MAAM;AAC3C,SAAO,qBAAqB,OAAO,MAAM;AAC3C;","names":["import_client"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { x402Client, x402HTTPClient, x402ClientConfig } from '@x402/core/client';
|
|
2
|
+
export { PaymentPolicy, SchemeRegistration, SelectPaymentRequirements, x402Client, x402ClientConfig, x402HTTPClient } from '@x402/core/client';
|
|
3
|
+
export { decodePaymentResponseHeader } from '@x402/core/http';
|
|
4
|
+
export { Network, PaymentPayload, PaymentRequired, PaymentRequirements, SchemeNetworkClient } from '@x402/core/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enables the payment of APIs using the x402 payment protocol v2.
|
|
8
|
+
*
|
|
9
|
+
* This function wraps the native fetch API to automatically handle 402 Payment Required responses
|
|
10
|
+
* by creating and sending payment headers. It will:
|
|
11
|
+
* 1. Make the initial request
|
|
12
|
+
* 2. If a 402 response is received, parse the payment requirements
|
|
13
|
+
* 3. Create a payment header using the configured x402HTTPClient
|
|
14
|
+
* 4. Retry the request with the payment header
|
|
15
|
+
*
|
|
16
|
+
* @param fetch - The fetch function to wrap (typically globalThis.fetch)
|
|
17
|
+
* @param client - Configured x402Client or x402HTTPClient instance for handling payments
|
|
18
|
+
* @returns A wrapped fetch function that handles 402 responses automatically
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
|
|
23
|
+
* import { ExactEvmScheme } from '@x402/evm';
|
|
24
|
+
* import { ExactSvmScheme } from '@x402/svm';
|
|
25
|
+
*
|
|
26
|
+
* const client = new x402Client()
|
|
27
|
+
* .register('eip155:8453', new ExactEvmScheme(evmSigner))
|
|
28
|
+
* .register('solana:mainnet', new ExactSvmScheme(svmSigner))
|
|
29
|
+
* .register('eip155:1', new ExactEvmScheme(evmSigner), 1); // v1 protocol
|
|
30
|
+
*
|
|
31
|
+
* const fetchWithPay = wrapFetchWithPayment(fetch, client);
|
|
32
|
+
*
|
|
33
|
+
* // Make a request that may require payment
|
|
34
|
+
* const response = await fetchWithPay('https://api.example.com/paid-endpoint');
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @throws {Error} If no schemes are provided
|
|
38
|
+
* @throws {Error} If the request configuration is missing
|
|
39
|
+
* @throws {Error} If a payment has already been attempted for this request
|
|
40
|
+
* @throws {Error} If there's an error creating the payment header
|
|
41
|
+
*/
|
|
42
|
+
declare function wrapFetchWithPayment(fetch: typeof globalThis.fetch, client: x402Client | x402HTTPClient): (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a payment-enabled fetch function from a configuration object.
|
|
45
|
+
*
|
|
46
|
+
* @param fetch - The fetch function to wrap (typically globalThis.fetch)
|
|
47
|
+
* @param config - Configuration options including scheme registrations and selectors
|
|
48
|
+
* @returns A wrapped fetch function that handles 402 responses automatically
|
|
49
|
+
*/
|
|
50
|
+
declare function wrapFetchWithPaymentFromConfig(fetch: typeof globalThis.fetch, config: x402ClientConfig): (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
51
|
+
|
|
52
|
+
export { wrapFetchWithPayment, wrapFetchWithPaymentFromConfig };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { x402Client, x402HTTPClient } from "@x402/core/client";
|
|
3
|
+
import { x402Client as x402Client2, x402HTTPClient as x402HTTPClient2 } from "@x402/core/client";
|
|
4
|
+
import { decodePaymentResponseHeader } from "@x402/core/http";
|
|
5
|
+
function wrapFetchWithPayment(fetch, client) {
|
|
6
|
+
const httpClient = client instanceof x402HTTPClient ? client : new x402HTTPClient(client);
|
|
7
|
+
return async (input, init) => {
|
|
8
|
+
const response = await fetch(input, init);
|
|
9
|
+
if (response.status !== 402) {
|
|
10
|
+
return response;
|
|
11
|
+
}
|
|
12
|
+
let paymentRequired;
|
|
13
|
+
try {
|
|
14
|
+
const getHeader = (name) => response.headers.get(name);
|
|
15
|
+
let body;
|
|
16
|
+
try {
|
|
17
|
+
const responseText = await response.text();
|
|
18
|
+
if (responseText) {
|
|
19
|
+
body = JSON.parse(responseText);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
let paymentPayload;
|
|
30
|
+
try {
|
|
31
|
+
paymentPayload = await client.createPaymentPayload(paymentRequired);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Failed to create payment payload: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
38
|
+
if (!init) {
|
|
39
|
+
throw new Error("Missing fetch request configuration");
|
|
40
|
+
}
|
|
41
|
+
if (init.__is402Retry) {
|
|
42
|
+
throw new Error("Payment already attempted");
|
|
43
|
+
}
|
|
44
|
+
const newInit = {
|
|
45
|
+
...init,
|
|
46
|
+
headers: {
|
|
47
|
+
...init.headers || {},
|
|
48
|
+
...paymentHeaders,
|
|
49
|
+
"Access-Control-Expose-Headers": "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
|
|
50
|
+
},
|
|
51
|
+
__is402Retry: true
|
|
52
|
+
};
|
|
53
|
+
const secondResponse = await fetch(input, newInit);
|
|
54
|
+
return secondResponse;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function wrapFetchWithPaymentFromConfig(fetch, config) {
|
|
58
|
+
const client = x402Client.fromConfig(config);
|
|
59
|
+
return wrapFetchWithPayment(fetch, client);
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
decodePaymentResponseHeader,
|
|
63
|
+
wrapFetchWithPayment,
|
|
64
|
+
wrapFetchWithPaymentFromConfig,
|
|
65
|
+
x402Client2 as x402Client,
|
|
66
|
+
x402HTTPClient2 as x402HTTPClient
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import { x402Client, x402ClientConfig, x402HTTPClient } from \"@x402/core/client\";\nimport { type PaymentRequired } from \"@x402/core/types\";\n\n/**\n * Enables the payment of APIs using the x402 payment protocol v2.\n *\n * This function wraps the native fetch API to automatically handle 402 Payment Required responses\n * by creating and sending payment headers. It will:\n * 1. Make the initial request\n * 2. If a 402 response is received, parse the payment requirements\n * 3. Create a payment header using the configured x402HTTPClient\n * 4. Retry the request with the payment header\n *\n * @param fetch - The fetch function to wrap (typically globalThis.fetch)\n * @param client - Configured x402Client or x402HTTPClient instance for handling payments\n * @returns A wrapped fetch function that handles 402 responses automatically\n *\n * @example\n * ```typescript\n * import { wrapFetchWithPayment, x402Client } from '@x402/fetch';\n * import { ExactEvmScheme } from '@x402/evm';\n * import { ExactSvmScheme } from '@x402/svm';\n *\n * const client = new x402Client()\n * .register('eip155:8453', new ExactEvmScheme(evmSigner))\n * .register('solana:mainnet', new ExactSvmScheme(svmSigner))\n * .register('eip155:1', new ExactEvmScheme(evmSigner), 1); // v1 protocol\n *\n * const fetchWithPay = wrapFetchWithPayment(fetch, client);\n *\n * // Make a request that may require payment\n * const response = await fetchWithPay('https://api.example.com/paid-endpoint');\n * ```\n *\n * @throws {Error} If no schemes are provided\n * @throws {Error} If the request configuration is missing\n * @throws {Error} If a payment has already been attempted for this request\n * @throws {Error} If there's an error creating the payment header\n */\nexport function wrapFetchWithPayment(\n fetch: typeof globalThis.fetch,\n client: x402Client | x402HTTPClient,\n) {\n const httpClient = client instanceof x402HTTPClient ? client : new x402HTTPClient(client);\n\n return async (input: RequestInfo, init?: RequestInit) => {\n const response = await fetch(input, init);\n\n if (response.status !== 402) {\n return response;\n }\n\n // Parse payment requirements from response\n let paymentRequired: PaymentRequired;\n try {\n // Create getHeader function for case-insensitive header lookup\n const getHeader = (name: string) => response.headers.get(name);\n\n // Try to get from headers first (v2), then from body (v1)\n let body: PaymentRequired | undefined;\n try {\n const responseText = await response.text();\n if (responseText) {\n body = JSON.parse(responseText) as PaymentRequired;\n }\n } catch {\n // Ignore JSON parse errors - might be header-only response\n }\n\n paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);\n } catch (error) {\n throw new Error(\n `Failed to parse payment requirements: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n // Create payment payload (copy extensions from PaymentRequired)\n let paymentPayload;\n try {\n paymentPayload = await client.createPaymentPayload(paymentRequired);\n } catch (error) {\n throw new Error(\n `Failed to create payment payload: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n // Encode payment header\n const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);\n\n // Ensure we have request init\n if (!init) {\n throw new Error(\"Missing fetch request configuration\");\n }\n\n // Check if this is already a retry to prevent infinite loops\n if ((init as { __is402Retry?: boolean }).__is402Retry) {\n throw new Error(\"Payment already attempted\");\n }\n\n // Create new request with payment header\n const newInit = {\n ...init,\n headers: {\n ...(init.headers || {}),\n ...paymentHeaders,\n \"Access-Control-Expose-Headers\": \"PAYMENT-RESPONSE,X-PAYMENT-RESPONSE\",\n },\n __is402Retry: true,\n };\n\n // Retry the request with payment\n const secondResponse = await fetch(input, newInit);\n return secondResponse;\n };\n}\n\n/**\n * Creates a payment-enabled fetch function from a configuration object.\n *\n * @param fetch - The fetch function to wrap (typically globalThis.fetch)\n * @param config - Configuration options including scheme registrations and selectors\n * @returns A wrapped fetch function that handles 402 responses automatically\n */\nexport function wrapFetchWithPaymentFromConfig(\n fetch: typeof globalThis.fetch,\n config: x402ClientConfig,\n) {\n const client = x402Client.fromConfig(config);\n return wrapFetchWithPayment(fetch, client);\n}\n\n// Re-export types and utilities for convenience\nexport { x402Client, x402HTTPClient } from \"@x402/core/client\";\nexport type {\n PaymentPolicy,\n SchemeRegistration,\n SelectPaymentRequirements,\n x402ClientConfig,\n} from \"@x402/core/client\";\nexport { decodePaymentResponseHeader } from \"@x402/core/http\";\nexport type {\n Network,\n PaymentPayload,\n PaymentRequired,\n PaymentRequirements,\n SchemeNetworkClient,\n} from \"@x402/core/types\";\n"],"mappings":";AAAA,SAAS,YAA8B,sBAAsB;AAoI7D,SAAS,cAAAA,aAAY,kBAAAC,uBAAsB;AAO3C,SAAS,mCAAmC;AApGrC,SAAS,qBACd,OACA,QACA;AACA,QAAM,aAAa,kBAAkB,iBAAiB,SAAS,IAAI,eAAe,MAAM;AAExF,SAAO,OAAO,OAAoB,SAAuB;AACvD,UAAM,WAAW,MAAM,MAAM,OAAO,IAAI;AAExC,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,IACT;AAGA,QAAI;AACJ,QAAI;AAEF,YAAM,YAAY,CAAC,SAAiB,SAAS,QAAQ,IAAI,IAAI;AAG7D,UAAI;AACJ,UAAI;AACF,cAAM,eAAe,MAAM,SAAS,KAAK;AACzC,YAAI,cAAc;AAChB,iBAAO,KAAK,MAAM,YAAY;AAAA,QAChC;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,wBAAkB,WAAW,2BAA2B,WAAW,IAAI;AAAA,IACzE,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACnG;AAAA,IACF;AAGA,QAAI;AACJ,QAAI;AACF,uBAAiB,MAAM,OAAO,qBAAqB,eAAe;AAAA,IACpE,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MAC/F;AAAA,IACF;AAGA,UAAM,iBAAiB,WAAW,6BAA6B,cAAc;AAG7E,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAGA,QAAK,KAAoC,cAAc;AACrD,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAGA,UAAM,UAAU;AAAA,MACd,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAI,KAAK,WAAW,CAAC;AAAA,QACrB,GAAG;AAAA,QACH,iCAAiC;AAAA,MACnC;AAAA,MACA,cAAc;AAAA,IAChB;AAGA,UAAM,iBAAiB,MAAM,MAAM,OAAO,OAAO;AACjD,WAAO;AAAA,EACT;AACF;AASO,SAAS,+BACd,OACA,QACA;AACA,QAAM,SAAS,WAAW,WAAW,MAAM;AAC3C,SAAO,qBAAqB,OAAO,MAAM;AAC3C;","names":["x402Client","x402HTTPClient"]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x402/fetch",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
"author": "Coinbase Inc.",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"main": "./dist/cjs/index.js",
|
|
5
|
+
"module": "./dist/esm/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"keywords": [],
|
|
10
8
|
"license": "Apache-2.0",
|
|
11
|
-
"
|
|
9
|
+
"author": "Coinbase Inc.",
|
|
10
|
+
"repository": "https://github.com/coinbase/x402",
|
|
11
|
+
"description": "x402 Payment Protocol Fetch Extension",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@eslint/js": "^9.24.0",
|
|
14
|
+
"@types/node": "^22.13.4",
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
16
|
+
"@typescript-eslint/parser": "^8.29.1",
|
|
17
|
+
"eslint": "^9.24.0",
|
|
18
|
+
"eslint-plugin-import": "^2.31.0",
|
|
19
|
+
"eslint-plugin-jsdoc": "^50.6.9",
|
|
20
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
21
|
+
"prettier": "3.5.2",
|
|
22
|
+
"tsup": "^8.4.0",
|
|
23
|
+
"tsx": "^4.19.2",
|
|
24
|
+
"typescript": "^5.7.3",
|
|
25
|
+
"vite": "^6.2.6",
|
|
26
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
27
|
+
"vitest": "^3.0.5"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"viem": "^2.39.3",
|
|
31
|
+
"zod": "^3.24.2",
|
|
32
|
+
"@x402/core": "^2.1.0"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/esm/index.d.mts",
|
|
38
|
+
"default": "./dist/esm/index.mjs"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/cjs/index.d.ts",
|
|
42
|
+
"default": "./dist/cjs/index.js"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"start": "tsx --env-file=.env index.ts",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"watch": "tsc --watch",
|
|
55
|
+
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
|
|
56
|
+
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
|
|
57
|
+
"lint": "eslint . --ext .ts --fix",
|
|
58
|
+
"lint:check": "eslint . --ext .ts"
|
|
59
|
+
}
|
|
12
60
|
}
|
package/index.js
DELETED