@dexterai/x402 3.8.0 → 3.9.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 CHANGED
@@ -5,14 +5,13 @@
5
5
  <h1 align="center">@dexterai/x402</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Full-stack x402 SDK. Add paid API monetization to any endpoint. Solana, Base, Polygon, Arbitrum, Optimism, Avalanche, BSC, and SKALE.</strong>
8
+ <strong>HTTP-native micropayments for agents. Solana and the major EVM chains.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@dexterai/x402"><img src="https://img.shields.io/npm/v/@dexterai/x402.svg" alt="npm"></a>
13
13
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E=18-brightgreen.svg" alt="Node"></a>
14
14
  <a href="https://dexter.cash/sdk"><img src="https://img.shields.io/badge/Live_Demo-dexter.cash%2Fsdk-blueviolet" alt="Live Demo"></a>
15
- <a href="https://dexter.cash/opendexter"><img src="https://img.shields.io/badge/Marketplace-5%2C000%2B_APIs-success" alt="Marketplace"></a>
16
15
  </p>
17
16
 
18
17
  <p align="center">
@@ -23,206 +22,142 @@
23
22
 
24
23
  ## What is x402?
25
24
 
26
- x402 is a protocol for HTTP-native micropayments. When a server returns HTTP status `402 Payment Required`, it includes payment details in a `PAYMENT-REQUIRED` header. The client signs a payment transaction and retries the request with a `PAYMENT-SIGNATURE` header. The server verifies and settles the payment, then returns the protected content.
25
+ x402 is HTTP's missing payment protocol. A server returns `402 Payment Required` with a `PAYMENT-REQUIRED` header describing what it wants paid; the client signs a payment, retries with `PAYMENT-SIGNATURE`, and gets the resource.
27
26
 
28
- This SDK handles the entire flow automatically—you just call `fetch()` and payments happen transparently. With **Access Pass** mode, buyers pay once and get unlimited access for a time window—no per-request signing needed.
27
+ The audience this is built for in 2026 is **agents**: Claude, ChatGPT, Cursor, and the rest, making paid HTTP calls on behalf of humans. This SDK is the buyer side and the seller side, with USDC on Solana and the major EVM chains, behind a single API.
29
28
 
30
- ---
31
-
32
- ## Why This SDK?
33
-
34
- **Monetize any API in minutes.** Add payments to your server in ~10 lines. Clients pay automatically—no checkout pages, no subscriptions, no invoices. Just HTTP.
35
-
36
- **Dynamic pricing.** Charge based on usage: characters, tokens, records, pixels, API calls—whatever makes sense. Price scales with input, not fixed rates.
37
-
38
- **Token-accurate LLM pricing.** Built-in [tiktoken](https://github.com/openai/tiktoken) support prices AI requests by actual token count. Works with OpenAI models out of the box, or bring your own rates for Anthropic, Gemini, Mistral, or local models.
39
-
40
- **Access Pass.** Pay once, get unlimited access for a time window. Buyers connect a wallet, make one payment, and receive a JWT token that works like an API key—no per-request signing, no private keys in code. The Stripe replacement for crypto-native APIs.
41
-
42
- **Full-stack.** Client SDK for browsers, server SDK for backends. React hooks, Express middleware patterns, facilitator client—everything you need.
43
-
44
- **Multi-chain.** Solana plus seven EVM chains (Base, Polygon, Arbitrum, Optimism, Avalanche, BSC, SKALE Base) with the same API. Add wallets for both families and the SDK picks the right one automatically.
45
-
46
- **Works out of the box.** Built-in RPC proxy, pre-flight balance checks, automatic retry on 402. Uses the [Dexter facilitator](https://x402.dexter.cash) by default—Solana's most feature-rich x402 facilitator.
29
+ You call `payAndFetch()` on the client. You add `x402Middleware()` on the server. Payments happen.
47
30
 
48
31
  ---
49
32
 
50
- ## Automatic Marketplace Discovery
51
-
52
- When someone pays for your API through the Dexter facilitator, your endpoint is **automatically discovered and listed** in the [OpenDexter Marketplace](https://dexter.cash/opendexter) — a searchable directory of 5,000+ paid APIs used by AI agents.
53
-
54
- No registration step needed. The flow:
55
-
56
- 1. You add `x402Middleware` to your endpoint (see Quick Start below)
57
- 2. An agent pays for your API → the facilitator processes the settlement
58
- 3. Your endpoint is auto-discovered, AI-named, and quality-verified
59
- 4. Agents find it via `x402_search` in any MCP client (ChatGPT, Claude, Cursor, etc.)
60
-
61
- Quality-verified endpoints (score 75+) get promoted in search results. The verification bot tests your endpoint automatically — no action required on your part.
62
-
63
- ---
64
-
65
- ## Quick Start
66
-
67
- ### Install
33
+ ## Quick start
68
34
 
69
35
  ```bash
70
36
  npm install @dexterai/x402
71
37
  ```
72
38
 
73
- ### Client (Node.js)
74
-
75
- The simplest way to make x402 payments from scripts:
39
+ ### Pay for a resource (Node.js, any chain)
76
40
 
77
41
  ```typescript
78
- import { wrapFetch } from '@dexterai/x402/client';
79
-
80
- // Solana
81
- const x402Fetch = wrapFetch(fetch, {
82
- walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
83
- });
84
-
85
- // EVM (Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE)
86
- const x402Fetch = wrapFetch(fetch, {
87
- evmPrivateKey: process.env.EVM_PRIVATE_KEY, // requires: npm install viem
88
- });
89
-
90
- // Both — SDK picks the chain with balance
91
- const x402Fetch = wrapFetch(fetch, {
92
- walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
93
- evmPrivateKey: process.env.EVM_PRIVATE_KEY,
94
- });
42
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
95
43
 
96
- // That's it. 402 responses are handled automatically.
97
- const response = await x402Fetch('https://api.example.com/protected');
98
- ```
99
-
100
- Check the payment receipt:
44
+ const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
45
+ const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
101
46
 
102
- ```typescript
103
- import { wrapFetch, getPaymentReceipt } from '@dexterai/x402/client';
104
-
105
- const x402Fetch = wrapFetch(fetch, { walletPrivateKey: process.env.SOLANA_PRIVATE_KEY });
106
- const response = await x402Fetch('https://api.example.com/protected');
47
+ const result = await payAndFetch(
48
+ 'https://api.example.com/protected',
49
+ { method: 'GET' },
50
+ { solana, evm },
51
+ {},
52
+ );
107
53
 
108
- const receipt = getPaymentReceipt(response);
109
- if (receipt) {
110
- console.log('Paid:', receipt.transaction, 'on', receipt.network);
54
+ if (result.ok && result.paid) {
55
+ const data = await result.response.json();
56
+ console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
57
+ } else if (result.ok && !result.paid) {
58
+ // Endpoint didn't demand payment; response came through unchanged.
59
+ const data = await result.response.json();
60
+ } else {
61
+ console.error(result.reason, result.detail);
111
62
  }
112
63
  ```
113
64
 
114
- ### Client (Browser)
115
-
116
- ```typescript
117
- import { createX402Client } from '@dexterai/x402/client';
118
-
119
- const client = createX402Client({
120
- wallets: {
121
- solana: solanaWallet,
122
- evm: evmWallet,
123
- },
124
- });
125
-
126
- // That's it. 402 responses are handled automatically.
127
- const response = await client.fetch('https://api.example.com/protected');
128
- ```
129
-
130
- RPC URLs are optional—the SDK uses Dexter's RPC proxy by default. Override if needed:
131
-
132
- ```typescript
133
- const client = createX402Client({
134
- wallets: { solana: solanaWallet },
135
- rpcUrls: {
136
- 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'https://your-rpc.com',
137
- },
138
- });
139
- ```
65
+ `payAndFetch` is version-agnostic (handles x402 v1 and v2 transparently) and returns a discriminated `PayResult`. The `ok: true` branch is further split by `paid: true | false`, so a free 200 response is distinguishable from an actually-paid one. No throws for expected failures.
140
66
 
141
- ### React
67
+ ### Pay for a resource (Browser, React)
142
68
 
143
- Works with [`@solana/wallet-adapter-react`](https://github.com/anza-xyz/wallet-adapter) and [`wagmi`](https://wagmi.sh/) out of the box:
69
+ `useX402Payment` accepts wallets from your existing providers (`@solana/wallet-adapter-react`, `wagmi`) and exposes a `fetch` that pays automatically.
144
70
 
145
71
  ```tsx
146
72
  import { useX402Payment } from '@dexterai/x402/react';
147
- import { useWallet } from '@solana/wallet-adapter-react'; // Solana
148
- import { useAccount } from 'wagmi'; // EVM (Base)
73
+ import { useWallet } from '@solana/wallet-adapter-react';
74
+ import { useAccount } from 'wagmi';
149
75
 
150
- function PayButton() {
151
- // Get wallets from your existing providers
76
+ function PayButton({ url }: { url: string }) {
152
77
  const solanaWallet = useWallet();
153
78
  const evmWallet = useAccount();
154
79
 
155
80
  const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
156
- wallets: {
157
- solana: solanaWallet, // Pass directly - SDK handles the interface
158
- evm: evmWallet,
159
- },
81
+ wallets: { solana: solanaWallet, evm: evmWallet },
160
82
  });
161
83
 
162
84
  return (
163
85
  <div>
164
86
  <p>Balance: ${balances[0]?.balance.toFixed(2)}</p>
165
- <button
166
- onClick={() => fetch('/api/protected')}
167
- disabled={isLoading || !solanaWallet.connected}
168
- >
169
- {isLoading ? 'Paying...' : 'Pay'}
87
+ <button onClick={() => fetch(url)} disabled={isLoading}>
88
+ {isLoading ? 'Paying…' : 'Pay'}
170
89
  </button>
171
- {transactionUrl && <a href={transactionUrl}>View Transaction</a>}
90
+ {transactionUrl && <a href={transactionUrl}>View transaction</a>}
172
91
  </div>
173
92
  );
174
93
  }
175
94
  ```
176
95
 
96
+ ### Protect an endpoint (server)
97
+
98
+ ```typescript
99
+ import express from 'express';
100
+ import { x402Middleware } from '@dexterai/x402/server';
101
+
102
+ const app = express();
103
+
104
+ app.get(
105
+ '/api/protected',
106
+ x402Middleware({
107
+ payTo: 'YourReceivingAddress',
108
+ amount: '0.01', // $0.01 USDC
109
+ network: 'eip155:8453', // Base. Pass an array for multi-chain.
110
+ }),
111
+ (req, res) => res.json({ data: 'protected content' }),
112
+ );
113
+ ```
114
+
115
+ The handler only runs after a successful payment. Pass `network` as an array to accept across multiple chains; the buyer picks the chain they have balance on.
116
+
117
+ ### Reading the receipt
118
+
119
+ `getPaymentReceipt(response)` returns the settled-payment info attached to any paid response (whether the payment came from `payAndFetch`, the legacy `wrapFetch`, or the React hook).
120
+
121
+ ```typescript
122
+ import { payAndFetch, getPaymentReceipt } from '@dexterai/x402/client';
123
+
124
+ const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
125
+ if (result.ok && result.paid) {
126
+ const receipt = getPaymentReceipt(result.response);
127
+ console.log('tx:', receipt?.transaction, 'on', receipt?.network);
128
+ }
129
+ ```
130
+
177
131
  ---
178
132
 
179
133
  ## Batch settlement (EVM)
180
134
 
181
- Batch settlement lets a buyer pre-fund an escrow channel once, make many
182
- **discrete** paid API calls against it with cheap off-chain vouchers, and then
183
- close the channel — the seller's many charges are batched into a handful of
184
- on-chain transactions instead of one per call. It amortizes gas across
185
- high-frequency discrete purchasing.
135
+ Batch settlement lets a buyer pre-fund an escrow channel once, make many **discrete** paid API calls against it with cheap off-chain vouchers, and then close the channel. The seller's many charges are batched into a handful of on-chain transactions instead of one per call. It amortizes gas across high-frequency discrete purchasing.
186
136
 
187
- It is **not** a streaming primitive it batches discrete purchases. EVM only
188
- (Base, Arbitrum, Polygon). The buyer never needs a gas token: every step
189
- (deposit, voucher, claim, settle, refund) is signature-based; the Dexter
190
- facilitator submits the transactions and pays the gas.
137
+ It is **not** a streaming primitive; it batches discrete purchases. EVM only (Base, Arbitrum, Polygon). The buyer never needs a gas token: every step (deposit, voucher, claim, settle, refund) is signature-based; the Dexter facilitator submits the transactions and pays the gas.
191
138
 
192
139
  ### Buyer
193
140
 
194
141
  ```ts
195
142
  import { openBatchChannel } from '@dexterai/x402/batch-settlement';
196
143
 
197
- // Open a channel — signs an escrow deposit authorization (no gas needed).
198
144
  const channel = await openBatchChannel({
199
145
  wallet: evmWallet, // any { address, signTypedData }
200
146
  network: 'eip155:8453', // Base
201
147
  deposit: '0.30', // USDC escrowed for this channel
202
148
  });
203
149
 
204
- // Each call signs a cumulative voucher against the channel.
205
150
  const a = await channel.fetch('https://api.example.com/v1/data');
206
151
  const b = await channel.fetch('https://api.example.com/v1/data');
207
152
 
208
153
  console.log(channel.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
209
154
 
210
- // Done buying — signal you're finished with the channel.
211
155
  const { closed } = await channel.close();
212
- // closed === true. This is an intent signal, not a settlement: it does not
213
- // move funds. The seller's settlement (below) claims what you spent and
214
- // refunds the rest of your escrow automatically on the normal path.
215
156
  ```
216
157
 
217
- Each `openBatchChannel` call opens a **new** channel: a fresh random
218
- channel-config salt is generated, so a buyer can hold several independent
219
- channels with the same seller over time. The salt is exposed as
220
- `channel.salt` — persist it if you will later need to resume that exact
221
- channel.
158
+ Each `openBatchChannel` call opens a new channel: a fresh random channel-config salt is generated, so a buyer can hold several independent channels with the same seller over time. The salt is exposed as `channel.salt`; persist it if you will later need to resume that exact channel.
222
159
 
223
- To resume a channel after a process restart, call `resumeBatchChannel` with
224
- the wallet, network, and the **salt** of the channel being resumed (the
225
- `channelId` alone is not enough — it cannot be reversed to a salt):
160
+ Resume after a process restart with the wallet, network, and the channel's salt:
226
161
 
227
162
  ```ts
228
163
  import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';
@@ -230,89 +165,182 @@ import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';
230
165
  const channel = await resumeBatchChannel({
231
166
  wallet: evmWallet,
232
167
  network: 'eip155:8453',
233
- salt: savedSalt, // channel.salt captured at open time
168
+ salt: savedSalt,
234
169
  });
235
170
  ```
236
171
 
237
- Channel state auto-persists (localStorage in the browser, a file under
238
- `~/.dexter-x402/channels` in Node); the resumed channel's accounting is
239
- recovered from storage, or from on-chain state if storage was lost. Pass a
240
- custom `store` (any `ChannelStore`) to persist elsewhere. To deterministically
241
- reopen a specific channel instead of getting a fresh one, pass an explicit
242
- `salt` to `openBatchChannel`.
172
+ Channel state auto-persists (localStorage in the browser, a file under `~/.dexter-x402/channels` in Node); the resumed channel's accounting is recovered from storage, or from on-chain state if storage was lost.
243
173
 
244
- #### Escape hatch `forceWithdraw()` / `finalizeWithdraw()`
174
+ #### Escape hatch: `forceWithdraw()` / `finalizeWithdraw()`
245
175
 
246
- If the seller never settles, the buyer can reclaim unspent escrow directly via
247
- the channel contract's timed withdrawal:
176
+ If the seller never settles, the buyer can reclaim unspent escrow directly via the channel contract's timed withdrawal:
248
177
 
249
178
  ```ts
250
- // 1. Start the on-chain timed withdrawal.
251
179
  await channel.forceWithdraw();
252
-
253
- // 2. After the channel's withdraw delay elapses, finalize it.
180
+ // after the channel's withdraw delay elapses
254
181
  await channel.finalizeWithdraw();
255
182
  ```
256
183
 
257
- This is a last-resort safety net — **normal operation never needs it**, since
258
- the seller's settlement returns unspent escrow on the standard path. Unlike
259
- every other batch-settlement step, the escape hatch **costs the buyer gas**: it
260
- submits transactions itself, so the buyer's wallet must be transaction-capable
261
- (it must expose a `sendTransaction` method). A signature-only wallet cannot use
262
- the escape hatch.
184
+ Last-resort safety net; normal operation never needs it. Unlike every other batch-settlement step, the escape hatch costs the buyer gas: the wallet must expose a `sendTransaction` method.
263
185
 
264
186
  ### Seller
265
187
 
266
- `createBatchSettlementSeller(config)` returns a callable object that **is** an
267
- Express request handler — mount it directly. It accepts batch-settlement
268
- payments (each incoming voucher is verified and persisted to channel storage)
269
- and collects them (claim → settle → refund). Dexter operates the delegate
270
- authorizer, so the seller manages no signing key:
188
+ `createBatchSettlementSeller(config)` returns an Express request handler. Mount it directly; it accepts vouchers, persists them, and settles in the background. Dexter operates the delegate authorizer, so the seller manages no signing key.
271
189
 
272
190
  ```ts
273
191
  import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
274
192
 
275
193
  const seller = createBatchSettlementSeller({
276
194
  payTo: '0xYourReceivingAddress',
277
- network: 'eip155:8453', // Base
278
- price: '0.08', // USDC per call
195
+ network: 'eip155:8453',
196
+ price: '0.08',
279
197
  });
280
198
 
281
- // The seller object IS the Express handler — mount it directly.
282
199
  app.use('/api/data', seller);
283
200
 
284
- // Auto-settlement runs in the background by default. On shutdown, stop the
285
- // seller — this flushes a final settle so no collected vouchers are lost.
286
201
  process.on('SIGTERM', async () => {
287
- await seller.stop();
202
+ await seller.stop(); // flushes a final settle so no vouchers are lost
288
203
  });
289
204
  ```
290
205
 
291
- Settlement happens automatically via a background loop (on by default). To
292
- settle on demand, call `seller.closeChannel(channelId)` for one channel or
293
- `seller.closeAll()` for every open channel.
206
+ Mounting via `x402Middleware` also works. With `scheme: 'batch-settlement'` it returns the same callable seller object, so you keep the `.stop()` / `.closeAll()` / `.closeChannel()` handle.
207
+
208
+ ---
294
209
 
295
- Mounting via `x402Middleware` also works — with `scheme: 'batch-settlement'` it
296
- returns the same callable seller object, so you still get a `.stop()` /
297
- `.closeAll()` / `.closeChannel()` handle:
210
+ ## Discovery (bazaar extension)
298
211
 
299
- ```ts
212
+ Shipped in 3.8.0. The bazaar extension makes any `x402Middleware`-protected route discoverable through the official x402 bazaar spec, so agents browsing a bazaar-compliant indexer find your endpoint by capability, not by URL.
213
+
214
+ The 402 response carries a spec-compliant `extensions.bazaar` block describing the route's inputs, output schema, and template path. Discovery indexers read it and surface your endpoint in agent-facing catalogs.
215
+
216
+ ```typescript
217
+ import {
218
+ x402Middleware,
219
+ bazaarExtension,
220
+ declareDiscoveryExtension,
221
+ } from '@dexterai/x402/server';
222
+
223
+ app.post(
224
+ '/v1/translate',
225
+ x402Middleware({
226
+ payTo: '...',
227
+ amount: '0.02',
228
+ network: 'eip155:8453',
229
+ extensions: [bazaarExtension()],
230
+ declarations: {
231
+ ...declareDiscoveryExtension({
232
+ method: 'POST',
233
+ bodyType: 'json',
234
+ inputSchema: {
235
+ properties: {
236
+ text: { type: 'string', description: 'Source text' },
237
+ targetLang: { type: 'string', description: 'ISO 639-1 code' },
238
+ },
239
+ required: ['text', 'targetLang'],
240
+ },
241
+ output: {
242
+ example: { translation: 'Bonjour' },
243
+ },
244
+ }),
245
+ },
246
+ }),
247
+ (req, res) => res.json({ translation: translate(req.body) }),
248
+ );
249
+ ```
250
+
251
+ `extensions` is opt-in: middleware without an `extensions` array emits a 402 byte-identical to pre-3.8.0 behavior. `method` may be omitted from `declareDiscoveryExtension`; the extension stamps the actual request method at 402 time.
252
+
253
+ Failure isolation: if an extension throws, it's caught, logged, and skipped. The 402 still goes out, just without that key. The payment path is never affected.
254
+
255
+ ---
256
+
257
+ ## Sponsored Access (Instinct ad network)
258
+
259
+ This is how MCP agents (Claude, ChatGPT, Cursor) see your sponsored placements. When an agent pays for an API through Dexter's facilitator, a matched recommendation can be injected into the settlement receipt; the agent's LLM reads it and may call the suggested resource next. Both blockchain transactions become proof of the conversion.
260
+
261
+ The buyer-side helpers are wired into every MCP `fetch` tool in the Dexter ecosystem, plus the human-facing receipt UI on x402gle. If you're shipping an x402 endpoint, sponsored access is how you reach the agents already using paid APIs.
262
+
263
+ ### Seller: enable recommendation injection
264
+
265
+ ```typescript
300
266
  import { x402Middleware } from '@dexterai/x402/server';
301
267
 
302
- const seller = x402Middleware({
303
- payTo: '0xYourReceivingAddress',
304
- amount: '0.08',
305
- network: 'eip155:8453',
306
- scheme: 'batch-settlement',
307
- });
268
+ app.get(
269
+ '/api/data',
270
+ x402Middleware({
271
+ payTo: '...',
272
+ amount: '0.01',
273
+ sponsoredAccess: true, // injects _x402_sponsored into JSON responses
274
+ }),
275
+ (req, res) => res.json({ data: 'content' }),
276
+ );
277
+ // Response: { _x402_sponsored: [{ resourceUrl, description, sponsor }], data: 'content' }
278
+ ```
308
279
 
309
- app.use('/api/data', seller);
310
- // seller.stop() / seller.closeAll() are available here too.
280
+ For custom placement (where in the body the recommendation appears, conversion logging, etc.), pass an object instead of `true`:
281
+
282
+ ```typescript
283
+ sponsoredAccess: {
284
+ inject: (body, recs) => ({ ...body, related_tools: recs }),
285
+ onMatch: (recs, settlement) => log(`matched ${recs.length} for tx ${settlement.transaction}`),
286
+ },
287
+ ```
288
+
289
+ ### Buyer: read recommendations off a paid response
290
+
291
+ ```typescript
292
+ import {
293
+ payAndFetch,
294
+ getSponsoredRecommendations,
295
+ fireImpressionBeacon,
296
+ } from '@dexterai/x402/client';
297
+
298
+ const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
299
+ if (result.ok && result.paid) {
300
+ const recs = getSponsoredRecommendations(result.response);
301
+ if (recs) {
302
+ for (const rec of recs) {
303
+ console.log(`${rec.sponsor}: ${rec.description} (${rec.resourceUrl})`);
304
+ }
305
+ await fireImpressionBeacon(result.response);
306
+ }
307
+ }
311
308
  ```
312
309
 
310
+ ### React: recommendations in the hook
311
+
312
+ ```tsx
313
+ import { useX402Payment } from '@dexterai/x402/react';
314
+
315
+ function PayButton() {
316
+ const { fetch, isLoading, sponsoredRecommendations } = useX402Payment({ wallets });
317
+
318
+ return (
319
+ <div>
320
+ <button onClick={() => fetch(url)} disabled={isLoading}>Pay</button>
321
+ {sponsoredRecommendations?.map((rec, i) => (
322
+ <a key={i} href={rec.resourceUrl}>{rec.sponsor}: {rec.description}</a>
323
+ ))}
324
+ </div>
325
+ );
326
+ }
327
+ ```
328
+
329
+ ### Advertise
330
+
331
+ Campaign creation is x402-gated at `x402ads.io`. Your wallet is your identity. Full advertiser guide at [docs.dexter.cash/docs/sponsored-access/for-advertisers](https://docs.dexter.cash/docs/sponsored-access/for-advertisers).
332
+
313
333
  ---
314
334
 
315
- ## Supported Networks
335
+ ## Auto-listing in OpenDexter
336
+
337
+ When an agent pays for your API through the Dexter facilitator, your endpoint is auto-discovered, AI-named, and quality-tested. Quality-verified endpoints surface in `x402_search` results across MCP clients (ChatGPT, Claude, Cursor). No registration step.
338
+
339
+ Browse the live catalog at [dexter.cash/opendexter](https://dexter.cash/opendexter).
340
+
341
+ ---
342
+
343
+ ## Supported networks
316
344
 
317
345
  All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supported). USDC on every chain.
318
346
 
@@ -338,25 +366,9 @@ All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supp
338
366
  | Base Sepolia | `eip155:84532` |
339
367
  | SKALE Base Sepolia | `eip155:324705682` |
340
368
 
341
- Accept payments on multiple chains simultaneously:
369
+ Multi-chain endpoints accept payments on any chain in the list. The buyer picks:
342
370
 
343
371
  ```typescript
344
- // Same address across all EVM chains
345
- app.get('/api/data', x402Middleware({
346
- payTo: '0xYourAddress',
347
- amount: '0.01',
348
- network: [
349
- 'eip155:8453', // Base
350
- 'eip155:137', // Polygon
351
- 'eip155:42161', // Arbitrum
352
- 'eip155:10', // Optimism
353
- 'eip155:43114', // Avalanche
354
- 'eip155:56', // BSC
355
- 'eip155:1187947933', // SKALE Base (zero gas)
356
- ],
357
- }));
358
-
359
- // Different addresses per chain family
360
372
  app.get('/api/data', x402Middleware({
361
373
  payTo: {
362
374
  'solana:*': 'YourSolanaAddress...',
@@ -378,40 +390,32 @@ app.get('/api/data', x402Middleware({
378
390
 
379
391
  ---
380
392
 
381
- ## Package Exports
393
+ ## Package exports
382
394
 
383
395
  ```typescript
384
- // Client - browser
385
- import { createX402Client } from '@dexterai/x402/client';
396
+ // Client: canonical entrypoint
397
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
386
398
 
387
- // Client - Node.js (private key wallet)
388
- import { wrapFetch, createKeypairWallet } from '@dexterai/x402/client';
399
+ // Client: sponsored access reader
400
+ import { getSponsoredRecommendations, fireImpressionBeacon } from '@dexterai/x402/client';
389
401
 
390
- // Batch settlement - EVM escrow channel buyer (gas amortization)
391
- import { openBatchChannel } from '@dexterai/x402/batch-settlement';
392
-
393
- // Batch settlement - EVM escrow channel seller runtime
394
- import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
395
-
396
- // React hook
402
+ // React
397
403
  import { useX402Payment } from '@dexterai/x402/react';
398
404
 
399
- // Server - Express middleware
405
+ // Server: middleware
400
406
  import { x402Middleware } from '@dexterai/x402/server';
401
407
 
402
- // Server - Access Pass (pay once, unlimited requests)
403
- import { x402AccessPass } from '@dexterai/x402/server';
408
+ // Server: discovery (bazaar extension)
409
+ import { bazaarExtension, declareDiscoveryExtension } from '@dexterai/x402/server';
404
410
 
405
- // Server - manual control
411
+ // Server: manual control
406
412
  import { createX402Server } from '@dexterai/x402/server';
407
413
 
408
- // Server - dynamic pricing
409
- import { createDynamicPricing, createTokenPricing } from '@dexterai/x402/server';
410
-
411
- // React - Access Pass hook
412
- import { useAccessPass } from '@dexterai/x402/react';
414
+ // Batch settlement
415
+ import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
416
+ import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
413
417
 
414
- // Chain adapters (advanced)
418
+ // Adapters (advanced)
415
419
  import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
416
420
 
417
421
  // Utilities
@@ -425,142 +429,17 @@ import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
425
429
  ```typescript
426
430
  import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
427
431
 
428
- // Convert dollars to atomic units (for API calls)
429
- toAtomicUnits(0.05, 6); // '50000'
430
- toAtomicUnits(1.50, 6); // '1500000'
431
-
432
- // Convert atomic units back to dollars (for display)
433
- fromAtomicUnits('50000', 6); // 0.05
434
- fromAtomicUnits(1500000n, 6); // 1.5
432
+ toAtomicUnits(0.05, 6); // '50000'
433
+ toAtomicUnits(1.50, 6); // '1500000'
434
+ fromAtomicUnits('50000', 6); // 0.05
435
+ fromAtomicUnits(1500000n, 6); // 1.5
435
436
  ```
436
437
 
437
438
  ---
438
439
 
439
- ## Server SDK
440
-
441
- ### Express Middleware — NEW!
442
-
443
- One-liner payment protection for any Express endpoint:
444
-
445
- ```typescript
446
- import express from 'express';
447
- import { x402Middleware } from '@dexterai/x402/server';
448
-
449
- const app = express();
450
-
451
- app.get('/api/protected',
452
- x402Middleware({
453
- payTo: 'YourSolanaAddress...',
454
- amount: '0.01', // $0.01 USD
455
- }),
456
- (req, res) => {
457
- // This only runs after successful payment
458
- res.json({ data: 'protected content' });
459
- }
460
- );
461
- ```
462
-
463
- Options:
464
- - `payTo` — Address to receive payments
465
- - `amount` — Price in USD (e.g., `'0.01'` for 1 cent)
466
- - `network` — CAIP-2 network (default: Solana mainnet)
467
- - `description` — Human-readable description
468
- - `facilitatorUrl` — Override facilitator (default: x402.dexter.cash)
469
- - `verbose` — Enable debug logging
470
-
471
- ### Access Pass — Pay Once, Unlimited Requests
440
+ ## Manual server (advanced)
472
441
 
473
- Replace API keys with time-limited access passes. Buyers make one payment and get a JWT token for unlimited requests during a time window.
474
-
475
- **Server:**
476
-
477
- ```typescript
478
- import express from 'express';
479
- import { x402AccessPass } from '@dexterai/x402/server';
480
-
481
- const app = express();
482
-
483
- // Protect all /api routes with access pass
484
- app.use('/api', x402AccessPass({
485
- payTo: 'YourSolanaAddress...',
486
- tiers: {
487
- '1h': '0.50', // $0.50 for 1 hour
488
- '24h': '2.00', // $2.00 for 24 hours
489
- },
490
- ratePerHour: '0.50', // also accept custom durations
491
- }));
492
-
493
- app.get('/api/data', (req, res) => {
494
- // Only runs with a valid access pass
495
- res.json({ data: 'premium content' });
496
- });
497
- ```
498
-
499
- **Client (Node.js):**
500
-
501
- ```typescript
502
- import { wrapFetch } from '@dexterai/x402/client';
503
-
504
- const x402Fetch = wrapFetch(fetch, {
505
- walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
506
- accessPass: { preferTier: '1h', maxSpend: '1.00' },
507
- });
508
-
509
- // First call: auto-purchases a 1-hour pass ($0.50 USDC)
510
- const res1 = await x402Fetch('https://api.example.com/api/data');
511
-
512
- // All subsequent calls for the next hour: uses cached JWT, zero payment
513
- const res2 = await x402Fetch('https://api.example.com/api/data');
514
- const res3 = await x402Fetch('https://api.example.com/api/data');
515
- ```
516
-
517
- **React:**
518
-
519
- ```tsx
520
- import { useAccessPass } from '@dexterai/x402/react';
521
-
522
- function Dashboard() {
523
- const { tiers, pass, isPassValid, purchasePass, fetch: apFetch } = useAccessPass({
524
- wallets: { solana: solanaWallet },
525
- resourceUrl: 'https://api.example.com',
526
- });
527
-
528
- return (
529
- <div>
530
- {!isPassValid && tiers?.map(t => (
531
- <button key={t.id} onClick={() => purchasePass(t.id)}>
532
- {t.label} — ${t.price}
533
- </button>
534
- ))}
535
- {isPassValid && <p>Pass active! {pass?.remainingSeconds}s remaining</p>}
536
- <button onClick={() => apFetch('/api/data')}>Fetch Data</button>
537
- </div>
538
- );
539
- }
540
- ```
541
-
542
- **How it works:**
543
- 1. Client requests a protected endpoint → Server returns `402` with `X-ACCESS-PASS-TIERS` header
544
- 2. Client selects a tier and pays via x402 → Server verifies, settles, issues a JWT
545
- 3. Server returns `200` with `ACCESS-PASS` header containing the JWT
546
- 4. Client caches the JWT and includes it as `Authorization: Bearer <token>` on all subsequent requests
547
- 5. Server validates the JWT locally (no facilitator call) → instant response
548
-
549
- Options:
550
- - `payTo` — Address to receive payments
551
- - `tiers` — Named duration tiers with prices (e.g., `{ '1h': '0.50' }`)
552
- - `ratePerHour` — Rate for custom durations (buyer sends `?duration=<seconds>`)
553
- - `network` — CAIP-2 network (default: Solana mainnet)
554
- - `secret` — HMAC secret for JWT signing (auto-generated if not provided)
555
- - `facilitatorUrl` — Override facilitator (default: x402.dexter.cash)
556
-
557
- **[Live demo →](https://dexter.cash/access-pass)**
558
-
559
- ---
560
-
561
- ### Manual Server (Advanced)
562
-
563
- For more control over the payment flow:
442
+ For full control over the payment flow without `x402Middleware`:
564
443
 
565
444
  ```typescript
566
445
  import { createX402Server } from '@dexterai/x402/server';
@@ -570,7 +449,6 @@ const server = createX402Server({
570
449
  network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
571
450
  });
572
451
 
573
- // In your route handler
574
452
  app.post('/protected', async (req, res) => {
575
453
  const paymentSig = req.headers['payment-signature'];
576
454
 
@@ -588,392 +466,121 @@ app.post('/protected', async (req, res) => {
588
466
  return res.status(402).json({ error: result.errorReason });
589
467
  }
590
468
 
591
- res.json({ data: 'Your protected content' });
469
+ res.json({ data: 'protected content' });
592
470
  });
593
471
  ```
594
472
 
595
473
  ---
596
474
 
597
- ## Dynamic Pricing
475
+ ## Legacy capabilities
598
476
 
599
- **Generic pricing for any use case** - charge by characters, bytes, API calls, or any unit you define. No external dependencies.
477
+ Several v1-era helpers ship with `@deprecated` markers in 3.9. They keep working. The markers exist to steer new code at the canonical paths. Each has a JSDoc pointing at its migration target.
600
478
 
601
- Works for:
602
- - LLM/AI endpoints (by character count)
603
- - Image processing (by pixel count or file size)
604
- - Data APIs (by record count)
605
- - Any service where cost scales with input
479
+ | Symbol | Migration target |
480
+ |---|---|
481
+ | `wrapFetch` (`@dexterai/x402/client`) | `payAndFetch` (version-agnostic, discriminated return type) |
482
+ | `createX402Client` (`@dexterai/x402/client`) | `payAndFetch` |
483
+ | `x402AccessPass`, `useAccessPass` | No replacement. Per-request `x402Middleware` + `payAndFetch` covers the same usage pattern. |
484
+ | `createDynamicPricing`, `createTokenPricing`, `MODEL_PRICING` | Price requests in your handler (use your model provider's live API for LLM cases) and pass the amount to `x402Middleware`. The v1 character-based and tiktoken-based helpers were stopgaps before x402 v2 dynamic pricing landed. |
485
+ | `stripePayTo` | No replacement in the SDK. Integrate Stripe at your application layer if needed. |
486
+ | `x402BrowserSupport` | No replacement. Build a custom paywall page if you need one. |
606
487
 
607
- ```typescript
608
- import { createX402Server, createDynamicPricing } from '@dexterai/x402/server';
609
-
610
- const server = createX402Server({ payTo: '...', network: '...' });
611
- const pricing = createDynamicPricing({
612
- unitSize: 1000, // chars per unit
613
- ratePerUnit: 0.01, // $0.01 per unit
614
- minUsd: 0.01, // floor
615
- maxUsd: 10.00, // ceiling
616
- });
617
-
618
- app.post('/api/llm', async (req, res) => {
619
- const { prompt } = req.body;
620
- const paymentSig = req.headers['payment-signature'];
621
-
622
- if (!paymentSig) {
623
- const quote = pricing.calculate(prompt);
624
- const requirements = await server.buildRequirements({
625
- amountAtomic: quote.amountAtomic,
626
- resourceUrl: req.originalUrl,
627
- });
628
- res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
629
- res.setHeader('X-Quote-Hash', quote.quoteHash);
630
- return res.status(402).json({ usdAmount: quote.usdAmount });
631
- }
632
-
633
- // Validate quote hasn't changed (prevents prompt manipulation)
634
- const quoteHash = req.headers['x-quote-hash'];
635
- if (!pricing.validateQuote(prompt, quoteHash)) {
636
- return res.status(400).json({ error: 'Prompt changed, re-quote required' });
637
- }
638
-
639
- const result = await server.settlePayment(paymentSig);
640
- if (!result.success) return res.status(402).json({ error: result.errorReason });
641
-
642
- const response = await runLLM(prompt);
643
- res.json(response);
644
- });
645
- ```
646
-
647
- The client SDK automatically forwards `X-Quote-Hash` on retry.
488
+ Removal release is TBD. Decided after we see how 3.9 deprecation warnings land with real consumers. None of these will be removed in 3.x.
648
489
 
649
490
  ---
650
491
 
651
- ## Token Pricing (LLM-Accurate)
652
-
653
- **Accurate token-based pricing for LLMs.** Uses tiktoken for token counting. Supports OpenAI models out of the box, plus custom rates for Anthropic, Gemini, Mistral, or any model.
492
+ ## API reference
654
493
 
655
- ```typescript
656
- import { createX402Server, createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';
494
+ ### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
657
495
 
658
- const server = createX402Server({ payTo: '...', network: '...' });
659
- const pricing = createTokenPricing({
660
- model: 'gpt-4o-mini', // Uses real OpenAI rates
661
- // minUsd: 0.001, // Optional floor
662
- // maxUsd: 50.0, // Optional ceiling
663
- });
496
+ | Argument | Type | Description |
497
+ |---|---|---|
498
+ | `url` | `string` | Endpoint to fetch |
499
+ | `init` | `RequestInit` | Standard fetch init. Body must be a string. |
500
+ | `wallets` | `WalletSet` | `{ solana?, evm? }`. The SDK picks the chain by what the merchant accepts and what you can pay |
501
+ | `opts` | `PayAndFetchOptions` | `maxAmountAtomic`, `timeoutMs`, `solanaRpcUrl` |
664
502
 
665
- app.post('/api/chat', async (req, res) => {
666
- const { prompt, systemPrompt } = req.body;
667
- const paymentSig = req.headers['payment-signature'];
668
-
669
- if (!paymentSig) {
670
- const quote = pricing.calculate(prompt, systemPrompt);
671
- const requirements = await server.buildRequirements({
672
- amountAtomic: quote.amountAtomic,
673
- resourceUrl: req.originalUrl,
674
- description: `${quote.model}: ${quote.inputTokens.toLocaleString()} tokens`,
675
- });
676
- res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
677
- res.setHeader('X-Quote-Hash', quote.quoteHash);
678
- return res.status(402).json({
679
- inputTokens: quote.inputTokens,
680
- usdAmount: quote.usdAmount,
681
- model: quote.model,
682
- tier: quote.tier,
683
- });
684
- }
685
-
686
- // Validate quote hasn't changed
687
- const quoteHash = req.headers['x-quote-hash'];
688
- if (!pricing.validateQuote(prompt, quoteHash)) {
689
- return res.status(400).json({ error: 'Prompt changed, re-quote required' });
690
- }
691
-
692
- const result = await server.settlePayment(paymentSig);
693
- if (!result.success) return res.status(402).json({ error: result.errorReason });
694
-
695
- const response = await openai.chat.completions.create({
696
- model: pricing.config.model,
697
- messages: [{ role: 'user', content: prompt }],
698
- max_completion_tokens: pricing.modelInfo.maxTokens,
699
- });
700
-
701
- res.json({
702
- response: response.choices[0].message.content,
703
- transaction: result.transaction,
704
- });
705
- });
706
- ```
707
-
708
- ### Available Models
503
+ `PayResult` is a discriminated union. Narrow on `ok` first, then on `paid`:
709
504
 
710
505
  ```typescript
711
- import { MODEL_PRICING, getAvailableModels } from '@dexterai/x402/server';
712
-
713
- // Get all models sorted by tier and price
714
- const models = getAvailableModels();
715
- // [{ model: 'gpt-5-nano', inputRate: 0.05, tier: 'fast' }, ...]
716
-
717
- // Check pricing for a specific model
718
- MODEL_PRICING['gpt-4o-mini'];
719
- // { input: 0.15, output: 0.6, maxTokens: 4096, tier: 'fast' }
720
- ```
721
-
722
- **Supported tiers:** `fast`, `standard`, `reasoning`, `premium`, `custom`
723
-
724
- ### Custom Models (Anthropic, Gemini, etc.)
725
-
726
- Not using OpenAI? Pass your own rates:
727
-
728
- ```typescript
729
- // Anthropic Claude
730
- const pricing = createTokenPricing({
731
- model: 'claude-3-sonnet',
732
- inputRate: 3.0, // $3.00 per 1M input tokens
733
- outputRate: 15.0, // $15.00 per 1M output tokens
734
- maxTokens: 4096,
735
- });
736
-
737
- // Google Gemini
738
- const pricing = createTokenPricing({
739
- model: 'gemini-1.5-pro',
740
- inputRate: 1.25,
741
- outputRate: 5.0,
742
- });
743
-
744
- // Custom/local model with custom tokenizer
745
- const pricing = createTokenPricing({
746
- model: 'llama-3-70b',
747
- inputRate: 0.50,
748
- tokenizer: (text) => llamaTokenizer.encode(text).length,
749
- });
750
- ```
751
-
752
- tiktoken's default encoding works well for most transformer models. Only use a custom tokenizer if your model has significantly different tokenization.
753
-
754
- ---
755
-
756
- ## Sponsored Access (Ads for Agents)
757
-
758
- Sponsored Access delivers targeted resource recommendations through x402 payments. When an agent pays for an API, the facilitator can inject a recommendation for a related tool in the settlement receipt. The agent sees the recommendation and can call it immediately -- the subsequent call is tracked as a conversion with both blockchain transaction hashes as proof.
759
-
760
- ### Server — Enable Recommendation Injection
761
-
762
- Add `sponsoredAccess: true` to your middleware config. This reads `extensions["sponsored-access"]` from the facilitator's settlement response and injects `_x402_sponsored` into the JSON response body so the agent's LLM can see the recommendations (headers are invisible to LLMs).
763
-
764
- ```typescript
765
- import { x402Middleware } from '@dexterai/x402/server';
766
-
767
- // Default injection: adds _x402_sponsored field to JSON response
768
- app.get('/api/data',
769
- x402Middleware({
770
- payTo: '...', amount: '0.01',
771
- sponsoredAccess: true,
772
- }),
773
- (req, res) => res.json({ data: 'content' })
774
- );
775
- // Agent receives: { _x402_sponsored: [{ resourceUrl, description, sponsor }], data: 'content' }
776
-
777
- // Custom injection: control where recommendations appear
778
- app.get('/api/data',
779
- x402Middleware({
780
- payTo: '...', amount: '0.01',
781
- sponsoredAccess: {
782
- inject: (body, recs) => ({ ...body, related_tools: recs }),
783
- onMatch: (recs, settlement) => {
784
- console.log(`Matched ${recs.length} recommendations for tx ${settlement.transaction}`);
785
- },
786
- },
787
- }),
788
- (req, res) => res.json({ data: 'content' })
789
- );
790
- ```
791
-
792
- ### Client — Read Recommendations
793
-
794
- ```typescript
795
- import {
796
- wrapFetch,
797
- getSponsoredRecommendations,
798
- fireImpressionBeacon,
799
- } from '@dexterai/x402/client';
800
-
801
- const x402Fetch = wrapFetch(fetch, { walletPrivateKey: key });
802
- const response = await x402Fetch('https://api.example.com/data');
803
-
804
- // Extract typed recommendations from the payment receipt
805
- const recs = getSponsoredRecommendations(response);
806
- if (recs) {
807
- for (const rec of recs) {
808
- console.log(`${rec.sponsor}: ${rec.description} -- ${rec.resourceUrl}`);
809
- }
810
- // Confirm delivery to the ad network
811
- await fireImpressionBeacon(response);
506
+ if (result.ok && result.paid) {
507
+ result.response; // the merchant's response
508
+ result.amountPaid; // amount actually paid, in the token's smallest denomination
509
+ result.network; // NetworkRef { caip2, bare, family }
510
+ result.txSignature; // optional; tx hash where the chain reports one
511
+ } else if (result.ok && !result.paid) {
512
+ result.response; // the merchant didn't demand payment; pass-through
513
+ } else {
514
+ result.reason; // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
515
+ result.detail; // verbatim merchant error for settlement_failed
812
516
  }
813
517
  ```
814
518
 
815
- ### React — Recommendations in Hooks
816
-
817
- ```tsx
818
- import { useX402Payment } from '@dexterai/x402/react';
819
-
820
- function PayButton() {
821
- const {
822
- fetch,
823
- isLoading,
824
- sponsoredRecommendations, // auto-populated after payment
825
- } = useX402Payment({ wallets });
826
-
827
- return (
828
- <div>
829
- <button onClick={() => fetch(url)} disabled={isLoading}>Pay</button>
830
- {sponsoredRecommendations?.map((rec, i) => (
831
- <a key={i} href={rec.resourceUrl}>{rec.sponsor}: {rec.description}</a>
832
- ))}
833
- </div>
834
- );
835
- }
836
- ```
837
-
838
- ### Types
839
-
840
- All types are re-exported from `@dexterai/x402-ads-types`:
841
-
842
- ```typescript
843
- import type { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402/client';
844
-
845
- interface SponsoredRecommendation {
846
- resourceUrl: string; // The URL to call
847
- description: string; // Agent-readable description
848
- sponsor: string; // Brand name
849
- bazaarId?: string; // Bazaar catalog ID
850
- price?: string; // Cost in atomic units
851
- currency?: string; // e.g., "USDC"
852
- }
853
- ```
854
-
855
- ### How It Works
856
-
857
- 1. Agent pays for an API via x402
858
- 2. Facilitator settles payment and calls the ad network's match API
859
- 3. If a campaign matches (by URL pattern, category, network), a recommendation is injected into `SettlementResponse.extensions["sponsored-access"]`
860
- 4. Publisher middleware (with `sponsoredAccess: true`) injects it into the JSON response body
861
- 5. Agent's LLM sees the recommendation and can call the suggested resource
862
- 6. If the agent calls it, the facilitator records a conversion with both tx hashes as proof
863
-
864
- ### Advertise Your API
865
-
866
- Want your API recommended to agents across the x402 network? Create and fund campaigns through the Agent API — no signup, no accounts. Your wallet is your identity.
867
-
868
- ```typescript
869
- // All campaign management is x402-gated at x402ads.io
870
- const x402Fetch = wrapFetch(fetch, { walletPrivateKey: key });
871
-
872
- // Create a campaign ($0.10 USDC)
873
- const res = await x402Fetch('https://x402ads.io/v1/agent/campaigns', {
874
- method: 'POST',
875
- headers: { 'Content-Type': 'application/json' },
876
- body: JSON.stringify({
877
- name: 'Promote My API',
878
- rec_resource_url: 'https://api.example.com/data',
879
- rec_description: 'Real-time market data',
880
- rec_sponsor_name: 'Example Corp',
881
- target_categories: ['defi', 'data'],
882
- bid_strategy: 'cpa',
883
- max_bid_amount: '50000',
884
- budget_daily: '5000000',
885
- }),
886
- });
887
- ```
888
-
889
- Full advertiser guide: [docs.dexter.cash/docs/sponsored-access/for-advertisers](https://docs.dexter.cash/docs/sponsored-access/for-advertisers)
890
-
891
- ---
892
-
893
- ## API Reference
894
-
895
- ### `createX402Client(options)`
896
-
897
- | Option | Type | Required | Description |
898
- |--------|------|----------|-------------|
899
- | `wallets` | `{ solana?, evm? }` | Yes | Multi-chain wallets |
900
- | `wallet` | `SolanaWallet` | No | Single Solana wallet (legacy) |
901
- | `preferredNetwork` | `string` | No | Prefer this network when multiple options available |
902
- | `rpcUrls` | `Record<string, string>` | No | RPC endpoints per network (defaults to Dexter proxy) |
903
- | `maxAmountAtomic` | `string` | No | Maximum payment cap |
904
- | `verbose` | `boolean` | No | Enable debug logging |
905
-
906
- ### `x402AccessPass(options)`
907
-
908
- | Option | Type | Required | Description |
909
- |--------|------|----------|-------------|
910
- | `payTo` | `string` | Yes | Address to receive payments |
911
- | `tiers` | `Record<string, string>` | One of `tiers` or `ratePerHour` | Named tiers (e.g., `{ '1h': '0.50' }`) |
912
- | `ratePerHour` | `string` | One of `tiers` or `ratePerHour` | USD rate for custom durations |
913
- | `network` | `string` | No | CAIP-2 network (default: Solana mainnet) |
914
- | `secret` | `Buffer` | No | HMAC secret for JWT (auto-generated) |
915
- | `facilitatorUrl` | `string` | No | Facilitator URL (default: x402.dexter.cash) |
916
- | `verbose` | `boolean` | No | Enable debug logging |
917
-
918
- ### `useX402Payment(options)`
919
-
920
- Returns:
921
-
922
- | Property | Type | Description |
923
- |----------|------|-------------|
924
- | `fetch` | `function` | Payment-aware fetch |
925
- | `isLoading` | `boolean` | Payment in progress |
926
- | `status` | `string` | `'idle'` \| `'pending'` \| `'success'` \| `'error'` |
927
- | `error` | `X402Error?` | Error details if failed |
928
- | `transactionId` | `string?` | Transaction signature |
929
- | `transactionUrl` | `string?` | Block explorer link |
930
- | `balances` | `Balance[]` | Token balances per chain |
931
- | `refreshBalances` | `function` | Manual refresh |
932
- | `reset` | `function` | Clear state |
933
- | `accessPass` | `object?` | Active pass state (tier, expiresAt, remainingSeconds) |
934
-
935
- ### `useAccessPass(options)`
519
+ ### `x402Middleware(config)`
936
520
 
937
521
  | Option | Type | Required | Description |
938
- |--------|------|----------|-------------|
939
- | `wallets` | `{ solana?, evm? }` | Yes | Multi-chain wallets |
940
- | `resourceUrl` | `string` | Yes | The x402 resource base URL |
941
- | `preferredNetwork` | `string` | No | Prefer this network |
942
- | `autoConnect` | `boolean` | No | Auto-fetch tiers on mount (default: true) |
943
-
944
- Returns:
945
-
946
- | Property | Type | Description |
947
- |----------|------|-------------|
948
- | `tiers` | `AccessPassTier[]?` | Available tiers from server |
949
- | `pass` | `object?` | Active pass (jwt, tier, expiresAt, remainingSeconds) |
950
- | `isPassValid` | `boolean` | Whether pass is active and not expired |
951
- | `purchasePass` | `function` | Buy a pass for a tier or custom duration |
952
- | `isPurchasing` | `boolean` | Purchase in progress |
953
- | `fetch` | `function` | Fetch with auto pass inclusion |
522
+ |---|---|---|---|
523
+ | `payTo` | `string \| { 'solana:*'?, 'eip155:*'?, [caip2]? }` | Yes | Receiver address; map for per-chain receivers |
524
+ | `amount` | `string` | Yes | USD amount, e.g., `'0.01'` |
525
+ | `network` | `string \| string[]` | No | CAIP-2 network(s). Default: Solana mainnet |
526
+ | `description` | `string` | No | Human-readable description |
527
+ | `scheme` | `'exact' \| 'batch-settlement'` | No | Use `'batch-settlement'` to mount as a batch-settlement seller |
528
+ | `extensions` | `ResourceServerExtension[]` | No | E.g., `[bazaarExtension()]` |
529
+ | `declarations` | `Record<string, unknown>` | No | Per-route extension config (see `declareDiscoveryExtension`) |
530
+ | `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Enable Instinct ad-network recommendation injection |
531
+ | `facilitatorUrl` | `string` | No | Override facilitator (default: `x402.dexter.cash`) |
532
+ | `verbose` | `boolean` | No | Debug logging |
533
+
534
+ ### `useX402Payment({ wallets })`
535
+
536
+ Returns `{ fetch, isLoading, status, error, transactionId, transactionUrl, balances, refreshBalances, reset, sponsoredRecommendations }`. Accepts wallets directly from `@solana/wallet-adapter-react` and `wagmi`, with no manual adapter wrapping.
537
+
538
+ ### `createBatchSettlementSeller(config)`
539
+
540
+ | Option | Type | Description |
541
+ |---|---|---|
542
+ | `payTo` | `string` | EVM receiver |
543
+ | `network` | `string` | CAIP-2 network |
544
+ | `price` | `string` | Per-call USD price |
545
+ | `storage` | `ChannelStorage` | Optional. Defaults to file storage under `~/.dexter-x402/channels` |
546
+
547
+ Returns an Express handler with `.stop()`, `.closeAll()`, `.closeChannel(channelId)`.
548
+
549
+ ### `bazaarExtension()` / `declareDiscoveryExtension(config)`
550
+
551
+ The bazaar extension factory takes no arguments. Per-route discovery config is supplied through `declareDiscoveryExtension(config)`:
552
+
553
+ | Field | Type | Notes |
554
+ |---|---|---|
555
+ | `method` | `'GET' \| 'HEAD' \| 'DELETE' \| 'POST' \| 'PUT' \| 'PATCH'` | Optional. If omitted, the actual request method is used. |
556
+ | `queryParams` | `Record<string, ParamSpec>` | For GET/HEAD/DELETE routes |
557
+ | `bodyType` | `'json' \| 'form'` | For POST/PUT/PATCH routes |
558
+ | `body` | `Record<string, ParamSpec>` | For POST/PUT/PATCH routes |
559
+ | `inputSchema` | JSON Schema (Draft 2020-12) | Validates `info` |
560
+ | `output` | `{ example, schema? }` | Example response payload |
954
561
 
955
562
  ---
956
563
 
957
564
  ## Development
958
565
 
959
566
  ```bash
960
- npm run build # Build ESM + CJS
567
+ npm run build # ESM + CJS
961
568
  npm run dev # Watch mode
962
- npm run typecheck # TypeScript checks
569
+ npm run typecheck
570
+ npm test # 273 vitest tests
963
571
  ```
964
572
 
965
573
  ---
966
574
 
967
575
  ## License
968
576
 
969
- MIT see [LICENSE](./LICENSE)
577
+ MIT. See [LICENSE](./LICENSE).
970
578
 
971
579
  ---
972
580
 
973
581
  <p align="center">
974
- <a href="https://x402.dexter.cash">Dexter Facilitator</a> ·
975
- <a href="https://dexter.cash/opendexter">OpenDexter Marketplace</a> ·
976
- <a href="https://dexter.cash/sdk">Live Demo</a> ·
977
- <a href="https://dexter.cash/access-pass">Access Pass Demo</a> ·
582
+ <a href="https://x402.dexter.cash">Dexter Facilitator</a> ·
583
+ <a href="https://dexter.cash/opendexter">OpenDexter Marketplace</a> ·
584
+ <a href="https://dexter.cash/sdk">Live Demo</a> ·
978
585
  <a href="https://dexter.cash/onboard">Become a Seller</a>
979
586
  </p>