@dexterai/x402 3.8.1 → 3.9.1

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 (50) hide show
  1. package/README.md +313 -702
  2. package/dist/adapters/index.cjs +1 -1
  3. package/dist/adapters/index.d.cts +3 -3
  4. package/dist/adapters/index.d.ts +3 -3
  5. package/dist/adapters/index.js +1 -1
  6. package/dist/batch-settlement/index.d.cts +3 -3
  7. package/dist/batch-settlement/index.d.ts +3 -3
  8. package/dist/batch-settlement/seller/index.d.cts +4 -4
  9. package/dist/batch-settlement/seller/index.d.ts +4 -4
  10. package/dist/client/index.cjs +1 -1
  11. package/dist/client/index.d.cts +308 -238
  12. package/dist/client/index.d.ts +308 -238
  13. package/dist/client/index.js +1 -1
  14. package/dist/{constants-qU-4U3L-.d.cts → constants-D41hDAG6.d.cts} +13 -1
  15. package/dist/{constants-qU-4U3L-.d.ts → constants-D41hDAG6.d.ts} +13 -1
  16. package/dist/react/index.cjs +1 -1
  17. package/dist/react/index.d.cts +12 -4
  18. package/dist/react/index.d.ts +12 -4
  19. package/dist/react/index.js +1 -1
  20. package/dist/server/index.cjs +3 -3
  21. package/dist/server/index.d.cts +63 -5
  22. package/dist/server/index.d.ts +63 -5
  23. package/dist/server/index.js +3 -3
  24. package/dist/tab/adapters/solana/index.cjs +1 -0
  25. package/dist/tab/adapters/solana/index.d.cts +123 -0
  26. package/dist/tab/adapters/solana/index.d.ts +123 -0
  27. package/dist/tab/adapters/solana/index.js +1 -0
  28. package/dist/tab/index.cjs +6 -0
  29. package/dist/tab/index.d.cts +29 -0
  30. package/dist/tab/index.d.ts +29 -0
  31. package/dist/tab/index.js +6 -0
  32. package/dist/tab/seller/index.cjs +6 -0
  33. package/dist/tab/seller/index.d.cts +291 -0
  34. package/dist/tab/seller/index.d.ts +291 -0
  35. package/dist/tab/seller/index.js +6 -0
  36. package/dist/{types-XG8QvfyL.d.cts → types-C8lyIOmX.d.cts} +1 -1
  37. package/dist/{types-DllrEG_Z.d.ts → types-CTl7yVq6.d.ts} +1 -1
  38. package/dist/{types-pOwQlGEV.d.cts → types-CiPcPs0w.d.cts} +1 -1
  39. package/dist/{types-DDBPREEu.d.ts → types-D9VMq7In.d.ts} +1 -1
  40. package/dist/types-DIrmhiD-.d.cts +234 -0
  41. package/dist/types-DIrmhiD-.d.ts +234 -0
  42. package/dist/{types-htvWHuW3.d.cts → types-RxdlGPaG.d.cts} +122 -1
  43. package/dist/{types-htvWHuW3.d.ts → types-RxdlGPaG.d.ts} +122 -1
  44. package/dist/utils/index.cjs +1 -1
  45. package/dist/utils/index.d.cts +10 -1
  46. package/dist/utils/index.d.ts +10 -1
  47. package/dist/utils/index.js +1 -1
  48. package/dist/{sponsored-access-D96FgkQK.d.ts → x402-client-CHrU2Bs6.d.cts} +114 -63
  49. package/dist/{sponsored-access-D7H-womP.d.cts → x402-client-CzseAnIt.d.ts} +114 -63
  50. package/package.json +18 -1
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,146 @@
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.
29
+ You call `payAndFetch()` on the client. You add `x402Middleware()` on the server. Payments happen.
41
30
 
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.
31
+ Built against the official x402 v1 and v2 specs. Adds the multi-chain buyer and seller surface, the React hook, a discriminated `PayResult` type, and batch-settlement channels for high-frequency calls.
47
32
 
48
33
  ---
49
34
 
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
35
+ ## Quick start
68
36
 
69
37
  ```bash
70
38
  npm install @dexterai/x402
71
39
  ```
72
40
 
73
- ### Client (Node.js)
74
-
75
- The simplest way to make x402 payments from scripts:
41
+ ### Pay for a resource (Node.js, any chain)
76
42
 
77
43
  ```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
- });
44
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
89
45
 
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
- });
95
-
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:
101
-
102
- ```typescript
103
- import { wrapFetch, getPaymentReceipt } from '@dexterai/x402/client';
46
+ const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
47
+ const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
104
48
 
105
- const x402Fetch = wrapFetch(fetch, { walletPrivateKey: process.env.SOLANA_PRIVATE_KEY });
106
- const response = await x402Fetch('https://api.example.com/protected');
49
+ const result = await payAndFetch(
50
+ 'https://api.example.com/protected',
51
+ { method: 'GET' },
52
+ { solana, evm },
53
+ {},
54
+ );
107
55
 
108
- const receipt = getPaymentReceipt(response);
109
- if (receipt) {
110
- console.log('Paid:', receipt.transaction, 'on', receipt.network);
56
+ if (result.ok && result.paid) {
57
+ const data = await result.response.json();
58
+ console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
59
+ } else if (result.ok && !result.paid) {
60
+ // Endpoint didn't demand payment; response came through unchanged.
61
+ const data = await result.response.json();
62
+ } else {
63
+ console.error(result.reason, result.detail);
111
64
  }
112
65
  ```
113
66
 
114
- ### Client (Browser)
67
+ `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.
115
68
 
116
- ```typescript
117
- import { createX402Client } from '@dexterai/x402/client';
69
+ ### Pay for a resource (Browser, React)
118
70
 
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
- ```
140
-
141
- ### React
142
-
143
- Works with [`@solana/wallet-adapter-react`](https://github.com/anza-xyz/wallet-adapter) and [`wagmi`](https://wagmi.sh/) out of the box:
71
+ `useX402Payment` accepts wallets from your existing providers (`@solana/wallet-adapter-react`, `wagmi`) and exposes a `fetch` that pays automatically.
144
72
 
145
73
  ```tsx
146
74
  import { useX402Payment } from '@dexterai/x402/react';
147
- import { useWallet } from '@solana/wallet-adapter-react'; // Solana
148
- import { useAccount } from 'wagmi'; // EVM (Base)
75
+ import { useWallet } from '@solana/wallet-adapter-react';
76
+ import { useAccount } from 'wagmi';
149
77
 
150
- function PayButton() {
151
- // Get wallets from your existing providers
78
+ function PayButton({ url }: { url: string }) {
152
79
  const solanaWallet = useWallet();
153
80
  const evmWallet = useAccount();
154
81
 
155
82
  const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
156
- wallets: {
157
- solana: solanaWallet, // Pass directly - SDK handles the interface
158
- evm: evmWallet,
159
- },
83
+ wallets: { solana: solanaWallet, evm: evmWallet },
160
84
  });
161
85
 
162
86
  return (
163
87
  <div>
164
88
  <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'}
89
+ <button onClick={() => fetch(url)} disabled={isLoading}>
90
+ {isLoading ? 'Paying…' : 'Pay'}
170
91
  </button>
171
- {transactionUrl && <a href={transactionUrl}>View Transaction</a>}
92
+ {transactionUrl && <a href={transactionUrl}>View transaction</a>}
172
93
  </div>
173
94
  );
174
95
  }
175
96
  ```
176
97
 
98
+ ### Protect an endpoint (server)
99
+
100
+ ```typescript
101
+ import express from 'express';
102
+ import { x402Middleware } from '@dexterai/x402/server';
103
+
104
+ const app = express();
105
+
106
+ app.get(
107
+ '/api/protected',
108
+ x402Middleware({
109
+ payTo: 'YourReceivingAddress',
110
+ amount: '0.01', // $0.01 USDC
111
+ network: 'eip155:8453', // Base. Pass an array for multi-chain.
112
+ }),
113
+ (req, res) => res.json({ data: 'protected content' }),
114
+ );
115
+ ```
116
+
117
+ 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.
118
+
119
+ ### Reading the receipt
120
+
121
+ `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).
122
+
123
+ ```typescript
124
+ import { payAndFetch, getPaymentReceipt } from '@dexterai/x402/client';
125
+
126
+ const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
127
+ if (result.ok && result.paid) {
128
+ const receipt = getPaymentReceipt(result.response);
129
+ console.log('tx:', receipt?.transaction, 'on', receipt?.network);
130
+ }
131
+ ```
132
+
177
133
  ---
178
134
 
179
135
  ## Batch settlement (EVM)
180
136
 
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.
137
+ 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
138
 
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.
139
+ 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.
140
+
141
+ > **Coming: `@dexterai/x402/tab` — OTS-backed streaming payments.** A peer module for *continuous metered consumption* (tokens, bytes, frames, seconds) where the unit of billing is smaller than a request. One passkey-authorized session signs many vouchers; the seller verifies locally and demands a fresh signature before each chunk. The contract is locked (`@dexterai/x402/tab` + `@dexterai/x402/tab/seller` subpaths emit type stubs today); implementation is phased in [`docs/DESIGN-tab-streaming.md`](./docs/DESIGN-tab-streaming.md). Architectural roadmap on [github.com/Dexter-DAO/dexter-vault/issues](https://github.com/Dexter-DAO/dexter-vault/issues?q=is%3Aissue+label%3Aroadmap).
191
142
 
192
143
  ### Buyer
193
144
 
194
145
  ```ts
195
146
  import { openBatchChannel } from '@dexterai/x402/batch-settlement';
196
147
 
197
- // Open a channel — signs an escrow deposit authorization (no gas needed).
198
148
  const channel = await openBatchChannel({
199
149
  wallet: evmWallet, // any { address, signTypedData }
200
150
  network: 'eip155:8453', // Base
201
151
  deposit: '0.30', // USDC escrowed for this channel
202
152
  });
203
153
 
204
- // Each call signs a cumulative voucher against the channel.
205
154
  const a = await channel.fetch('https://api.example.com/v1/data');
206
155
  const b = await channel.fetch('https://api.example.com/v1/data');
207
156
 
208
157
  console.log(channel.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
209
158
 
210
- // Done buying — signal you're finished with the channel.
211
159
  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
160
  ```
216
161
 
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.
162
+ 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
163
 
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):
164
+ Resume after a process restart with the wallet, network, and the channel's salt:
226
165
 
227
166
  ```ts
228
167
  import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';
@@ -230,89 +169,182 @@ import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';
230
169
  const channel = await resumeBatchChannel({
231
170
  wallet: evmWallet,
232
171
  network: 'eip155:8453',
233
- salt: savedSalt, // channel.salt captured at open time
172
+ salt: savedSalt,
234
173
  });
235
174
  ```
236
175
 
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`.
176
+ 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
177
 
244
- #### Escape hatch `forceWithdraw()` / `finalizeWithdraw()`
178
+ #### Escape hatch: `forceWithdraw()` / `finalizeWithdraw()`
245
179
 
246
- If the seller never settles, the buyer can reclaim unspent escrow directly via
247
- the channel contract's timed withdrawal:
180
+ If the seller never settles, the buyer can reclaim unspent escrow directly via the channel contract's timed withdrawal:
248
181
 
249
182
  ```ts
250
- // 1. Start the on-chain timed withdrawal.
251
183
  await channel.forceWithdraw();
252
-
253
- // 2. After the channel's withdraw delay elapses, finalize it.
184
+ // after the channel's withdraw delay elapses
254
185
  await channel.finalizeWithdraw();
255
186
  ```
256
187
 
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.
188
+ 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
189
 
264
190
  ### Seller
265
191
 
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:
192
+ `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
193
 
272
194
  ```ts
273
195
  import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
274
196
 
275
197
  const seller = createBatchSettlementSeller({
276
198
  payTo: '0xYourReceivingAddress',
277
- network: 'eip155:8453', // Base
278
- price: '0.08', // USDC per call
199
+ network: 'eip155:8453',
200
+ price: '0.08',
279
201
  });
280
202
 
281
- // The seller object IS the Express handler — mount it directly.
282
203
  app.use('/api/data', seller);
283
204
 
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
205
  process.on('SIGTERM', async () => {
287
- await seller.stop();
206
+ await seller.stop(); // flushes a final settle so no vouchers are lost
288
207
  });
289
208
  ```
290
209
 
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.
210
+ Mounting via `x402Middleware` also works. With `scheme: 'batch-settlement'` it returns the same callable seller object, so you keep the `.stop()` / `.closeAll()` / `.closeChannel()` handle.
211
+
212
+ ---
294
213
 
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:
214
+ ## Discovery (bazaar extension)
298
215
 
299
- ```ts
216
+ 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.
217
+
218
+ 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.
219
+
220
+ ```typescript
221
+ import {
222
+ x402Middleware,
223
+ bazaarExtension,
224
+ declareDiscoveryExtension,
225
+ } from '@dexterai/x402/server';
226
+
227
+ app.post(
228
+ '/v1/translate',
229
+ x402Middleware({
230
+ payTo: '...',
231
+ amount: '0.02',
232
+ network: 'eip155:8453',
233
+ extensions: [bazaarExtension()],
234
+ declarations: {
235
+ ...declareDiscoveryExtension({
236
+ method: 'POST',
237
+ bodyType: 'json',
238
+ inputSchema: {
239
+ properties: {
240
+ text: { type: 'string', description: 'Source text' },
241
+ targetLang: { type: 'string', description: 'ISO 639-1 code' },
242
+ },
243
+ required: ['text', 'targetLang'],
244
+ },
245
+ output: {
246
+ example: { translation: 'Bonjour' },
247
+ },
248
+ }),
249
+ },
250
+ }),
251
+ (req, res) => res.json({ translation: translate(req.body) }),
252
+ );
253
+ ```
254
+
255
+ `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.
256
+
257
+ 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.
258
+
259
+ ---
260
+
261
+ ## Sponsored Access (Instinct ad network)
262
+
263
+ 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.
264
+
265
+ 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.
266
+
267
+ ### Seller: enable recommendation injection
268
+
269
+ ```typescript
300
270
  import { x402Middleware } from '@dexterai/x402/server';
301
271
 
302
- const seller = x402Middleware({
303
- payTo: '0xYourReceivingAddress',
304
- amount: '0.08',
305
- network: 'eip155:8453',
306
- scheme: 'batch-settlement',
307
- });
272
+ app.get(
273
+ '/api/data',
274
+ x402Middleware({
275
+ payTo: '...',
276
+ amount: '0.01',
277
+ sponsoredAccess: true, // injects _x402_sponsored into JSON responses
278
+ }),
279
+ (req, res) => res.json({ data: 'content' }),
280
+ );
281
+ // Response: { _x402_sponsored: [{ resourceUrl, description, sponsor }], data: 'content' }
282
+ ```
308
283
 
309
- app.use('/api/data', seller);
310
- // seller.stop() / seller.closeAll() are available here too.
284
+ For custom placement (where in the body the recommendation appears, conversion logging, etc.), pass an object instead of `true`:
285
+
286
+ ```typescript
287
+ sponsoredAccess: {
288
+ inject: (body, recs) => ({ ...body, related_tools: recs }),
289
+ onMatch: (recs, settlement) => log(`matched ${recs.length} for tx ${settlement.transaction}`),
290
+ },
291
+ ```
292
+
293
+ ### Buyer: read recommendations off a paid response
294
+
295
+ ```typescript
296
+ import {
297
+ payAndFetch,
298
+ getSponsoredRecommendations,
299
+ fireImpressionBeacon,
300
+ } from '@dexterai/x402/client';
301
+
302
+ const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
303
+ if (result.ok && result.paid) {
304
+ const recs = getSponsoredRecommendations(result.response);
305
+ if (recs) {
306
+ for (const rec of recs) {
307
+ console.log(`${rec.sponsor}: ${rec.description} (${rec.resourceUrl})`);
308
+ }
309
+ await fireImpressionBeacon(result.response);
310
+ }
311
+ }
311
312
  ```
312
313
 
314
+ ### React: recommendations in the hook
315
+
316
+ ```tsx
317
+ import { useX402Payment } from '@dexterai/x402/react';
318
+
319
+ function PayButton() {
320
+ const { fetch, isLoading, sponsoredRecommendations } = useX402Payment({ wallets });
321
+
322
+ return (
323
+ <div>
324
+ <button onClick={() => fetch(url)} disabled={isLoading}>Pay</button>
325
+ {sponsoredRecommendations?.map((rec, i) => (
326
+ <a key={i} href={rec.resourceUrl}>{rec.sponsor}: {rec.description}</a>
327
+ ))}
328
+ </div>
329
+ );
330
+ }
331
+ ```
332
+
333
+ ### Advertise
334
+
335
+ 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).
336
+
313
337
  ---
314
338
 
315
- ## Supported Networks
339
+ ## Auto-listing in OpenDexter
340
+
341
+ 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.
342
+
343
+ Browse the live catalog at [dexter.cash/opendexter](https://dexter.cash/opendexter).
344
+
345
+ ---
346
+
347
+ ## Supported networks
316
348
 
317
349
  All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supported). USDC on every chain.
318
350
 
@@ -338,25 +370,9 @@ All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supp
338
370
  | Base Sepolia | `eip155:84532` |
339
371
  | SKALE Base Sepolia | `eip155:324705682` |
340
372
 
341
- Accept payments on multiple chains simultaneously:
373
+ Multi-chain endpoints accept payments on any chain in the list. The buyer picks:
342
374
 
343
375
  ```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
376
  app.get('/api/data', x402Middleware({
361
377
  payTo: {
362
378
  'solana:*': 'YourSolanaAddress...',
@@ -378,40 +394,32 @@ app.get('/api/data', x402Middleware({
378
394
 
379
395
  ---
380
396
 
381
- ## Package Exports
397
+ ## Package exports
382
398
 
383
399
  ```typescript
384
- // Client - browser
385
- import { createX402Client } from '@dexterai/x402/client';
400
+ // Client: canonical entrypoint
401
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
386
402
 
387
- // Client - Node.js (private key wallet)
388
- import { wrapFetch, createKeypairWallet } from '@dexterai/x402/client';
403
+ // Client: sponsored access reader
404
+ import { getSponsoredRecommendations, fireImpressionBeacon } from '@dexterai/x402/client';
389
405
 
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
406
+ // React
397
407
  import { useX402Payment } from '@dexterai/x402/react';
398
408
 
399
- // Server - Express middleware
409
+ // Server: middleware
400
410
  import { x402Middleware } from '@dexterai/x402/server';
401
411
 
402
- // Server - Access Pass (pay once, unlimited requests)
403
- import { x402AccessPass } from '@dexterai/x402/server';
412
+ // Server: discovery (bazaar extension)
413
+ import { bazaarExtension, declareDiscoveryExtension } from '@dexterai/x402/server';
404
414
 
405
- // Server - manual control
415
+ // Server: manual control
406
416
  import { createX402Server } from '@dexterai/x402/server';
407
417
 
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';
418
+ // Batch settlement
419
+ import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
420
+ import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
413
421
 
414
- // Chain adapters (advanced)
422
+ // Adapters (advanced)
415
423
  import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
416
424
 
417
425
  // Utilities
@@ -425,142 +433,17 @@ import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
425
433
  ```typescript
426
434
  import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
427
435
 
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
436
+ toAtomicUnits(0.05, 6); // '50000'
437
+ toAtomicUnits(1.50, 6); // '1500000'
438
+ fromAtomicUnits('50000', 6); // 0.05
439
+ fromAtomicUnits(1500000n, 6); // 1.5
435
440
  ```
436
441
 
437
442
  ---
438
443
 
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
444
+ ## Manual server (advanced)
472
445
 
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:
446
+ For full control over the payment flow without `x402Middleware`:
564
447
 
565
448
  ```typescript
566
449
  import { createX402Server } from '@dexterai/x402/server';
@@ -570,7 +453,6 @@ const server = createX402Server({
570
453
  network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
571
454
  });
572
455
 
573
- // In your route handler
574
456
  app.post('/protected', async (req, res) => {
575
457
  const paymentSig = req.headers['payment-signature'];
576
458
 
@@ -588,392 +470,121 @@ app.post('/protected', async (req, res) => {
588
470
  return res.status(402).json({ error: result.errorReason });
589
471
  }
590
472
 
591
- res.json({ data: 'Your protected content' });
473
+ res.json({ data: 'protected content' });
592
474
  });
593
475
  ```
594
476
 
595
477
  ---
596
478
 
597
- ## Dynamic Pricing
479
+ ## Legacy capabilities
598
480
 
599
- **Generic pricing for any use case** - charge by characters, bytes, API calls, or any unit you define. No external dependencies.
481
+ 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
482
 
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
483
+ | Symbol | Migration target |
484
+ |---|---|
485
+ | `wrapFetch` (`@dexterai/x402/client`) | `payAndFetch` (version-agnostic, discriminated return type) |
486
+ | `createX402Client` (`@dexterai/x402/client`) | `payAndFetch` |
487
+ | `x402AccessPass`, `useAccessPass` | No replacement. Per-request `x402Middleware` + `payAndFetch` covers the same usage pattern. |
488
+ | `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. |
489
+ | `stripePayTo` | No replacement in the SDK. Integrate Stripe at your application layer if needed. |
490
+ | `x402BrowserSupport` | No replacement. Build a custom paywall page if you need one. |
606
491
 
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.
492
+ None of these will be removed in 3.x.
648
493
 
649
494
  ---
650
495
 
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.
496
+ ## API reference
654
497
 
655
- ```typescript
656
- import { createX402Server, createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';
498
+ ### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
657
499
 
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
- });
500
+ | Argument | Type | Description |
501
+ |---|---|---|
502
+ | `url` | `string` | Endpoint to fetch |
503
+ | `init` | `RequestInit` | Standard fetch init. Body must be a string. |
504
+ | `wallets` | `WalletSet` | `{ solana?, evm? }`. The SDK picks the chain by what the merchant accepts and what you can pay |
505
+ | `opts` | `PayAndFetchOptions` | `maxAmountAtomic`, `timeoutMs`, `solanaRpcUrl` |
664
506
 
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
507
+ `PayResult` is a discriminated union. Narrow on `ok` first, then on `paid`:
709
508
 
710
509
  ```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);
510
+ if (result.ok && result.paid) {
511
+ result.response; // the merchant's response
512
+ result.amountPaid; // amount actually paid, in the token's smallest denomination
513
+ result.network; // NetworkRef { caip2, bare, family }
514
+ result.txSignature; // optional; tx hash where the chain reports one
515
+ } else if (result.ok && !result.paid) {
516
+ result.response; // the merchant didn't demand payment; pass-through
517
+ } else {
518
+ result.reason; // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
519
+ result.detail; // verbatim merchant error for settlement_failed
812
520
  }
813
521
  ```
814
522
 
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)`
523
+ ### `x402Middleware(config)`
936
524
 
937
525
  | 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 |
526
+ |---|---|---|---|
527
+ | `payTo` | `string \| { 'solana:*'?, 'eip155:*'?, [caip2]? }` | Yes | Receiver address; map for per-chain receivers |
528
+ | `amount` | `string` | Yes | USD amount, e.g., `'0.01'` |
529
+ | `network` | `string \| string[]` | No | CAIP-2 network(s). Default: Solana mainnet |
530
+ | `description` | `string` | No | Human-readable description |
531
+ | `scheme` | `'exact' \| 'batch-settlement'` | No | Use `'batch-settlement'` to mount as a batch-settlement seller |
532
+ | `extensions` | `ResourceServerExtension[]` | No | E.g., `[bazaarExtension()]` |
533
+ | `declarations` | `Record<string, unknown>` | No | Per-route extension config (see `declareDiscoveryExtension`) |
534
+ | `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Enable Instinct ad-network recommendation injection |
535
+ | `facilitatorUrl` | `string` | No | Override facilitator (default: `x402.dexter.cash`) |
536
+ | `verbose` | `boolean` | No | Debug logging |
537
+
538
+ ### `useX402Payment({ wallets })`
539
+
540
+ 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.
541
+
542
+ ### `createBatchSettlementSeller(config)`
543
+
544
+ | Option | Type | Description |
545
+ |---|---|---|
546
+ | `payTo` | `string` | EVM receiver |
547
+ | `network` | `string` | CAIP-2 network |
548
+ | `price` | `string` | Per-call USD price |
549
+ | `storage` | `ChannelStorage` | Optional. Defaults to file storage under `~/.dexter-x402/channels` |
550
+
551
+ Returns an Express handler with `.stop()`, `.closeAll()`, `.closeChannel(channelId)`.
552
+
553
+ ### `bazaarExtension()` / `declareDiscoveryExtension(config)`
554
+
555
+ The bazaar extension factory takes no arguments. Per-route discovery config is supplied through `declareDiscoveryExtension(config)`:
556
+
557
+ | Field | Type | Notes |
558
+ |---|---|---|
559
+ | `method` | `'GET' \| 'HEAD' \| 'DELETE' \| 'POST' \| 'PUT' \| 'PATCH'` | Optional. If omitted, the actual request method is used. |
560
+ | `queryParams` | `Record<string, ParamSpec>` | For GET/HEAD/DELETE routes |
561
+ | `bodyType` | `'json' \| 'form'` | For POST/PUT/PATCH routes |
562
+ | `body` | `Record<string, ParamSpec>` | For POST/PUT/PATCH routes |
563
+ | `inputSchema` | JSON Schema (Draft 2020-12) | Validates `info` |
564
+ | `output` | `{ example, schema? }` | Example response payload |
954
565
 
955
566
  ---
956
567
 
957
568
  ## Development
958
569
 
959
570
  ```bash
960
- npm run build # Build ESM + CJS
571
+ npm run build # ESM + CJS
961
572
  npm run dev # Watch mode
962
- npm run typecheck # TypeScript checks
573
+ npm run typecheck
574
+ npm test # 273 vitest tests
963
575
  ```
964
576
 
965
577
  ---
966
578
 
967
579
  ## License
968
580
 
969
- MIT see [LICENSE](./LICENSE)
581
+ MIT. See [LICENSE](./LICENSE).
970
582
 
971
583
  ---
972
584
 
973
585
  <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> ·
586
+ <a href="https://x402.dexter.cash">Dexter Facilitator</a> ·
587
+ <a href="https://dexter.cash/opendexter">OpenDexter Catalog</a> ·
588
+ <a href="https://dexter.cash/sdk">Live Demo</a> ·
978
589
  <a href="https://dexter.cash/onboard">Become a Seller</a>
979
590
  </p>