@dexterai/x402 3.17.0 → 3.18.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.
- package/README.md +126 -455
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/tab/adapters/solana/index.d.cts +1 -1
- package/dist/tab/adapters/solana/index.d.ts +1 -1
- package/dist/tab/index.cjs +1 -1
- package/dist/tab/index.d.cts +9 -7
- package/dist/tab/index.d.ts +9 -7
- package/dist/tab/index.js +1 -1
- package/dist/tab/seller/index.cjs +4 -4
- package/dist/tab/seller/index.d.cts +174 -8
- package/dist/tab/seller/index.d.ts +174 -8
- package/dist/tab/seller/index.js +4 -4
- package/dist/{types-ZjcxOAbW.d.ts → types-BL9QW1gf.d.ts} +1 -1
- package/dist/{types-B1wGPP7B.d.cts → types-DMzS_Rh2.d.cts} +1 -1
- package/dist/{types-DEnVPFxF.d.cts → types-DuoL3s8n.d.cts} +1 -1
- package/dist/{types-DEnVPFxF.d.ts → types-DuoL3s8n.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,13 +5,18 @@
|
|
|
5
5
|
<h1 align="center">@dexterai/x402</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Give your agent a spending limit it can't exceed.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Open a tab, set a cap, and your agent pays as it works, with no signature on each charge. Your money stays in your own wallet, and the seller is still guaranteed payment. Buyer and seller SDKs, on Solana and the major EVM chains.
|
|
9
13
|
</p>
|
|
10
14
|
|
|
11
15
|
<p align="center">
|
|
12
16
|
<a href="https://www.npmjs.com/package/@dexterai/x402"><img src="https://img.shields.io/npm/v/@dexterai/x402.svg" alt="npm"></a>
|
|
13
17
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E=18-brightgreen.svg" alt="Node"></a>
|
|
14
|
-
<
|
|
18
|
+
<img src="https://img.shields.io/badge/non--custodial-passkey-brightgreen" alt="Non-custodial">
|
|
19
|
+
<a href="https://dexter.cash/sdk"><img src="https://img.shields.io/badge/Try_it-real_payments-blueviolet" alt="Live Demo"></a>
|
|
15
20
|
</p>
|
|
16
21
|
|
|
17
22
|
<p align="center">
|
|
@@ -20,190 +25,75 @@
|
|
|
20
25
|
|
|
21
26
|
---
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## Why a tab
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
A tab gives an agent a spending limit that the Solana program enforces at consensus. You set a cap, the agent pays against it call by call with no signature on each charge, and your USDC stays in your own wallet the entire time.
|
|
26
31
|
|
|
27
|
-
The
|
|
32
|
+
The two older ways to let an agent spend each give up something a tab keeps. Prefunding an escrow moves your money to a custodian, so your balance is on the table and you have paid a stranger in advance. Handing a wallet a spending delegate keeps your custody but lets you withdraw the funds mid-charge, so the seller can be left unpaid and serious sellers decline it. A tab keeps both halves: the money never leaves your wallet, and while the tab is open the chain blocks you from pulling it out from under accrued charges. The seller gets paid when they settle, and settlement is automatic.
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
34
|
+
The closest familiar shape is an auth-and-capture card hold, with the hold enforced on-chain instead of by a processor.
|
|
32
35
|
|
|
33
36
|
---
|
|
34
37
|
|
|
35
|
-
##
|
|
38
|
+
## Install
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
41
|
npm install @dexterai/x402
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
|
|
45
|
-
|
|
46
|
-
const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
|
|
47
|
-
const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
|
|
48
|
-
|
|
49
|
-
const result = await payAndFetch(
|
|
50
|
-
'https://api.example.com/protected',
|
|
51
|
-
{ method: 'GET' },
|
|
52
|
-
{ solana, evm },
|
|
53
|
-
{},
|
|
54
|
-
);
|
|
55
|
-
|
|
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);
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
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.
|
|
68
|
-
|
|
69
|
-
### Pay for a resource (Browser, React)
|
|
70
|
-
|
|
71
|
-
`useX402Payment` accepts wallets from your existing providers (`@solana/wallet-adapter-react`, `wagmi`) and exposes a `fetch` that pays automatically.
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import { useX402Payment } from '@dexterai/x402/react';
|
|
75
|
-
import { useWallet } from '@solana/wallet-adapter-react';
|
|
76
|
-
import { useAccount } from 'wagmi';
|
|
77
|
-
|
|
78
|
-
function PayButton({ url }: { url: string }) {
|
|
79
|
-
const solanaWallet = useWallet();
|
|
80
|
-
const evmWallet = useAccount();
|
|
81
|
-
|
|
82
|
-
const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
|
|
83
|
-
wallets: { solana: solanaWallet, evm: evmWallet },
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<div>
|
|
88
|
-
<p>Balance: ${balances[0]?.balance.toFixed(2)}</p>
|
|
89
|
-
<button onClick={() => fetch(url)} disabled={isLoading}>
|
|
90
|
-
{isLoading ? 'Paying…' : 'Pay'}
|
|
91
|
-
</button>
|
|
92
|
-
{transactionUrl && <a href={transactionUrl}>View transaction</a>}
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
```
|
|
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
|
|
44
|
+
One install is both sides: the buyer surface at `@dexterai/x402/tab`, the seller surface at `@dexterai/x402/tab/seller`.
|
|
120
45
|
|
|
121
|
-
|
|
46
|
+
## Open a tab and pay (buyer)
|
|
122
47
|
|
|
123
|
-
|
|
124
|
-
import { payAndFetch, getPaymentReceipt } from '@dexterai/x402/client';
|
|
48
|
+
A buyer drives tabs through a `vault` adapter over their passkey-rooted Solana vault. Build it once from the vault's addresses, which you receive when you enroll at [dexter.cash](https://dexter.cash), plus your passkey signer:
|
|
125
49
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
50
|
+
```ts
|
|
51
|
+
import { createSolanaVaultAdapter } from '@dexterai/x402/tab/adapters/solana';
|
|
52
|
+
|
|
53
|
+
const vault = createSolanaVaultAdapter({
|
|
54
|
+
connection, // your Solana Connection (any RPC)
|
|
55
|
+
swigAddress, // the vault's Swig state account, from enrollment
|
|
56
|
+
vaultPda, // the vault's gate PDA, from enrollment
|
|
57
|
+
passkeySigner, // browser: WebAuthnAssertion; server agent: passkeySignerFromP256Keypair(kp)
|
|
58
|
+
feePayer, // lamport fee payer (a Signer)
|
|
59
|
+
});
|
|
131
60
|
```
|
|
132
61
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
## Batch settlement (EVM)
|
|
136
|
-
|
|
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.
|
|
138
|
-
|
|
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
|
-
---
|
|
142
|
-
|
|
143
|
-
## Tabs (Solana) — streaming micropayments, settled on close
|
|
144
|
-
|
|
145
|
-
`@dexterai/x402/tab` is the streaming peer of batch settlement: *continuous metered consumption* (tokens, bytes, frames, seconds) where the unit of billing is smaller than a request. One passkey-authorized, drain-protected session signs many vouchers off-chain; the seller verifies locally; one on-chain settle on close moves USDC from the buyer's vault to the seller. Non-custodial — funds never leave the buyer's vault until settle, and the vault freezes withdrawals while a tab is open so the seller can't be rugged. Design: [`docs/DESIGN-tab-streaming.md`](./docs/DESIGN-tab-streaming.md).
|
|
146
|
-
|
|
147
|
-
### Pay a URL — zero seller knowledge
|
|
148
|
-
|
|
149
|
-
Given ONLY a URL, the buyer discovers the seller from the URL's own standard
|
|
150
|
-
x402 `402` challenge, opens a freeze-protected tab to it, and pays. The seller
|
|
151
|
-
pubkey comes off the wire — never from your code.
|
|
62
|
+
Given only a URL, the buyer then reads the seller's terms from the URL's own `402` challenge, opens a lock-protected tab, and pays. The seller's address comes off the wire, never from your code:
|
|
152
63
|
|
|
153
64
|
```ts
|
|
154
65
|
import { payUrlWithTab } from '@dexterai/x402/tab';
|
|
155
66
|
|
|
156
|
-
const tabs = new Map(); //
|
|
67
|
+
const tabs = new Map(); // one open tab per seller, reused across calls
|
|
157
68
|
const { result, tab } = await payUrlWithTab(
|
|
158
69
|
'https://api.example.com/paid/infer',
|
|
159
70
|
{ method: 'GET' },
|
|
160
71
|
{ vault, perUnitCap: '0.01', totalCap: '1.00', tabs },
|
|
161
72
|
);
|
|
162
73
|
// ...more payUrlWithTab calls reuse the same tab via `tabs`...
|
|
163
|
-
await tab?.close(); //
|
|
74
|
+
await tab?.close(); // one on-chain settle for everything the agent spent
|
|
164
75
|
```
|
|
165
76
|
|
|
166
|
-
`
|
|
167
|
-
|
|
168
|
-
Use `resolveTabTerms` when you want a human-readable price and settlement descriptor — for consent UIs, directories, or agents that decide before acting:
|
|
77
|
+
To decide before you pay, `resolveTabTerms(url)` reads a URL's price and settlement terms without paying, for consent screens, directories, or an agent that plans ahead:
|
|
169
78
|
|
|
170
79
|
```ts
|
|
171
80
|
import { resolveTabTerms } from '@dexterai/x402/tab';
|
|
172
81
|
|
|
173
|
-
// Decide BEFORE you pay: what would a tab to this URL cost and settle into?
|
|
174
82
|
const resolved = await resolveTabTerms('https://api.example.com/paid/tick');
|
|
175
83
|
if (resolved.kind === 'terms') {
|
|
176
84
|
console.log(resolved.terms.counterparty, resolved.terms.perRequest.human);
|
|
177
|
-
// settlement: { custody: 'non-custodial', protection: '
|
|
85
|
+
// settlement: { custody: 'non-custodial', protection: 'lock', settleOn: 'close' }
|
|
178
86
|
}
|
|
179
87
|
```
|
|
180
88
|
|
|
181
|
-
|
|
89
|
+
## Accept tabs on your API (seller)
|
|
182
90
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
```ts
|
|
186
|
-
import { tabChallengeMiddleware, tabMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
|
|
187
|
-
|
|
188
|
-
app.get('/paid/infer',
|
|
189
|
-
tabChallengeMiddleware({ sellerPubkey, network: 'solana:mainnet', perUnit: '0.001', facilitatorUrl }),
|
|
190
|
-
tabMiddleware({ connection, sellerPubkey, network: 'solana:mainnet', perUnit: '0.001', settle: 'on-close', facilitatorUrl }),
|
|
191
|
-
async (req, res) => {
|
|
192
|
-
const meter = openSse(res, { tab: requireTab(req), perUnit: '0.001' });
|
|
193
|
-
await meter.charge(1);
|
|
194
|
-
meter.send('...');
|
|
195
|
-
meter.end();
|
|
196
|
-
});
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
For sellers who also want to accept one-shot payments (catalog verifiers, non-tab buyers), `tabOrExactMiddleware` is the recommended default — ONE middleware advertising both rails in a single 402 challenge:
|
|
91
|
+
`tabOrExactMiddleware` is the recommended default: one middleware that advertises a tab and a one-shot price in a single 402 challenge, so agents pay by tab and one-shot callers pay exact, at the same price.
|
|
200
92
|
|
|
201
93
|
```ts
|
|
202
94
|
import { tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
|
|
203
95
|
import type { X402Request } from '@dexterai/x402/server';
|
|
204
96
|
|
|
205
|
-
// ONE middleware, two rails: agents pay by tab; one-shot buyers (and
|
|
206
|
-
// catalog verifiers) pay exact. Same price on both.
|
|
207
97
|
app.get('/paid/tick',
|
|
208
98
|
tabOrExactMiddleware({ connection, sellerPubkey, network: 'solana:mainnet', perUnit: '0.01' }),
|
|
209
99
|
async (req, res) => {
|
|
@@ -212,219 +102,108 @@ app.get('/paid/tick',
|
|
|
212
102
|
const meter = openSse(res, { tab, perUnit: '0.01' });
|
|
213
103
|
await meter.charge(1);
|
|
214
104
|
meter.send(JSON.stringify({ data: '...' }));
|
|
215
|
-
meter.end();
|
|
105
|
+
await meter.end();
|
|
216
106
|
});
|
|
217
107
|
```
|
|
218
108
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
import { openBatchChannel } from '@dexterai/x402/batch-settlement';
|
|
223
|
-
|
|
224
|
-
const channel = await openBatchChannel({
|
|
225
|
-
wallet: evmWallet, // any { address, signTypedData }
|
|
226
|
-
network: 'eip155:8453', // Base
|
|
227
|
-
deposit: '0.30', // USDC escrowed for this channel
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const a = await channel.fetch('https://api.example.com/v1/data');
|
|
231
|
-
const b = await channel.fetch('https://api.example.com/v1/data');
|
|
232
|
-
|
|
233
|
-
console.log(channel.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
|
|
234
|
-
|
|
235
|
-
const { closed } = await channel.close();
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
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.
|
|
109
|
+
For a tab-only endpoint, compose the two middlewares directly: `tabChallengeMiddleware` (answers voucher-less requests with the standard x402 challenge, so any agent can discover you) before `tabMiddleware` (verifies the per-charge vouchers). Both are exported from `@dexterai/x402/tab/seller`.
|
|
239
110
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
import { resumeBatchChannel } from '@dexterai/x402/batch-settlement';
|
|
244
|
-
|
|
245
|
-
const channel = await resumeBatchChannel({
|
|
246
|
-
wallet: evmWallet,
|
|
247
|
-
network: 'eip155:8453',
|
|
248
|
-
salt: savedSalt,
|
|
249
|
-
});
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
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.
|
|
253
|
-
|
|
254
|
-
#### Escape hatch: `forceWithdraw()` / `finalizeWithdraw()`
|
|
255
|
-
|
|
256
|
-
If the seller never settles, the buyer can reclaim unspent escrow directly via the channel contract's timed withdrawal:
|
|
257
|
-
|
|
258
|
-
```ts
|
|
259
|
-
await channel.forceWithdraw();
|
|
260
|
-
// after the channel's withdraw delay elapses
|
|
261
|
-
await channel.finalizeWithdraw();
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
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.
|
|
265
|
-
|
|
266
|
-
### Seller
|
|
267
|
-
|
|
268
|
-
`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.
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
|
|
111
|
+
---
|
|
272
112
|
|
|
273
|
-
|
|
274
|
-
payTo: '0xYourReceivingAddress',
|
|
275
|
-
network: 'eip155:8453',
|
|
276
|
-
price: '0.08',
|
|
277
|
-
});
|
|
113
|
+
## How it works
|
|
278
114
|
|
|
279
|
-
|
|
115
|
+
Three nouns and one actor.
|
|
280
116
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
117
|
+
- **Vault:** your money, held in your own wallet and locked by your passkey. The program never takes custody.
|
|
118
|
+
- **Tab:** a capped spending limit you open against your vault, for one agent and one counterparty. The agent draws against it; the vault enforces the cap.
|
|
119
|
+
- **Passkey:** your key. You tap it to set up the vault and to open or approve a tab. Nothing else can authorize a withdrawal.
|
|
120
|
+
- **Your agent:** who you open the tab for.
|
|
285
121
|
|
|
286
|
-
|
|
122
|
+
A tab opens with one passkey tap, the agent spends against it with no further prompts, and one on-chain settle pays the seller and closes it. Each charge the agent makes is off-chain, so it costs no gas and no signature. As charges accrue, the seller crystallizes them on-chain into the reservation that guarantees its payment — keyless and automatic, with no action and no signature from you.
|
|
287
123
|
|
|
288
124
|
---
|
|
289
125
|
|
|
290
|
-
##
|
|
291
|
-
|
|
292
|
-
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.
|
|
293
|
-
|
|
294
|
-
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.
|
|
126
|
+
## Why you can trust it
|
|
295
127
|
|
|
296
|
-
|
|
297
|
-
import {
|
|
298
|
-
x402Middleware,
|
|
299
|
-
bazaarExtension,
|
|
300
|
-
declareDiscoveryExtension,
|
|
301
|
-
} from '@dexterai/x402/server';
|
|
302
|
-
|
|
303
|
-
app.post(
|
|
304
|
-
'/v1/translate',
|
|
305
|
-
x402Middleware({
|
|
306
|
-
payTo: '...',
|
|
307
|
-
amount: '0.02',
|
|
308
|
-
network: 'eip155:8453',
|
|
309
|
-
extensions: [bazaarExtension()],
|
|
310
|
-
declarations: {
|
|
311
|
-
...declareDiscoveryExtension({
|
|
312
|
-
method: 'POST',
|
|
313
|
-
bodyType: 'json',
|
|
314
|
-
inputSchema: {
|
|
315
|
-
properties: {
|
|
316
|
-
text: { type: 'string', description: 'Source text' },
|
|
317
|
-
targetLang: { type: 'string', description: 'ISO 639-1 code' },
|
|
318
|
-
},
|
|
319
|
-
required: ['text', 'targetLang'],
|
|
320
|
-
},
|
|
321
|
-
output: {
|
|
322
|
-
example: { translation: 'Bonjour' },
|
|
323
|
-
},
|
|
324
|
-
}),
|
|
325
|
-
},
|
|
326
|
-
}),
|
|
327
|
-
(req, res) => res.json({ translation: translate(req.body) }),
|
|
328
|
-
);
|
|
329
|
-
```
|
|
128
|
+
The word "unruggable" has to be earned, so here is what actually backs it. The properties below are enforced by the on-chain program, not by this SDK.
|
|
330
129
|
|
|
331
|
-
|
|
130
|
+
- **Non-custodial.** Your USDC stays in your own wallet. The program holds no funds; it records bindings and gates a withdrawal. There is no escrow account and no custodian to fail.
|
|
131
|
+
- **The cap is enforced on-chain.** The limit is checked by the Solana program at consensus, not by this library and not by Dexter. You can read the program and verify the cap yourself: [`Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc`](https://solscan.io/account/Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc) on Solana mainnet.
|
|
132
|
+
- **Only your passkey moves funds.** Withdrawals require a WebAuthn assertion verified by Solana's secp256r1 precompile. The SDK and the facilitator never hold a key that can drain your wallet.
|
|
133
|
+
- **The seller is protected, surgically.** As the agent spends, accrued charges crystallize on-chain into a reservation the buyer cannot withdraw out from under. The reservation is exactly the amount accrued — not the whole wallet — so the buyer keeps spending or withdrawing the rest of their balance freely while the tab stays open. The seller is guaranteed the accrued amount; the buyer is never locked out of funds they haven't committed. If a seller ever goes silent, the buyer recovers an abandoned reservation themselves after a fixed grace period; nobody's funds can be frozen indefinitely.
|
|
134
|
+
- **Live on Solana mainnet.** Tabs settle on mainnet today. We can demonstrate the program rejecting a forged passkey from a clone: see the [`dexter-vault`](https://github.com/Dexter-DAO/dexter-vault) program repo.
|
|
135
|
+
- **Pre-audit, and we say so.** Not yet externally audited; funding is in flight. The report and any findings publish in the program repo. Responsible disclosure: branch@dexter.cash.
|
|
332
136
|
|
|
333
|
-
|
|
137
|
+
The full threat model and trust assumptions live in the program's [`SECURITY.md`](https://github.com/Dexter-DAO/dexter-vault).
|
|
334
138
|
|
|
335
139
|
---
|
|
336
140
|
|
|
337
|
-
##
|
|
141
|
+
## Approving a tab is one hosted screen
|
|
338
142
|
|
|
339
|
-
|
|
143
|
+
When a partner's app opens a tab for a user, the approval runs on one Dexter-hosted consent screen, deep-linked from the partner's app. The user sees the counterparty, the cap, and the expiry, taps their passkey once, and control returns to the app. The partner builds no approval UI and never handles a passkey.
|
|
340
144
|
|
|
341
|
-
The
|
|
145
|
+
The screen is hosted by Dexter for a structural reason, not a stylistic one: the vault's passkey can only sign on Dexter's own origin, so a user cannot be phished into approving on a look-alike page. The safety is a property of where the key will sign. Flow and routing: [docs.dexter.cash/tabs](https://docs.dexter.cash). **[TODO: confirm final docs path once #5 lands.]**
|
|
342
146
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
import { x402Middleware } from '@dexterai/x402/server';
|
|
147
|
+
---
|
|
347
148
|
|
|
348
|
-
|
|
349
|
-
'/api/data',
|
|
350
|
-
x402Middleware({
|
|
351
|
-
payTo: '...',
|
|
352
|
-
amount: '0.01',
|
|
353
|
-
sponsoredAccess: true, // injects _x402_sponsored into JSON responses
|
|
354
|
-
}),
|
|
355
|
-
(req, res) => res.json({ data: 'content' }),
|
|
356
|
-
);
|
|
357
|
-
// Response: { _x402_sponsored: [{ resourceUrl, description, sponsor }], data: 'content' }
|
|
358
|
-
```
|
|
149
|
+
## One-shot payments
|
|
359
150
|
|
|
360
|
-
|
|
151
|
+
When a charge is a single discrete purchase rather than metered consumption, pay it one-shot. x402 is HTTP's payment protocol: a server returns `402 Payment Required` describing what it wants paid, the client signs and retries, and the resource comes back. USDC on Solana and the major EVM chains, behind one API.
|
|
361
152
|
|
|
362
153
|
```typescript
|
|
363
|
-
|
|
364
|
-
inject: (body, recs) => ({ ...body, related_tools: recs }),
|
|
365
|
-
onMatch: (recs, settlement) => log(`matched ${recs.length} for tx ${settlement.transaction}`),
|
|
366
|
-
},
|
|
367
|
-
```
|
|
154
|
+
import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
|
|
368
155
|
|
|
369
|
-
|
|
156
|
+
const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
|
|
157
|
+
const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
|
|
370
158
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
159
|
+
const result = await payAndFetch(
|
|
160
|
+
'https://api.example.com/protected',
|
|
161
|
+
{ method: 'GET' },
|
|
162
|
+
{ solana, evm },
|
|
163
|
+
{},
|
|
164
|
+
);
|
|
377
165
|
|
|
378
|
-
const result = await payAndFetch(url, { method: 'GET' }, wallets, {});
|
|
379
166
|
if (result.ok && result.paid) {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
167
|
+
const data = await result.response.json();
|
|
168
|
+
console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
|
|
169
|
+
} else if (result.ok && !result.paid) {
|
|
170
|
+
const data = await result.response.json(); // endpoint didn't demand payment; passed through
|
|
171
|
+
} else {
|
|
172
|
+
console.error(result.reason, result.detail);
|
|
387
173
|
}
|
|
388
174
|
```
|
|
389
175
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
```tsx
|
|
393
|
-
import { useX402Payment } from '@dexterai/x402/react';
|
|
176
|
+
`payAndFetch` handles x402 v1 and v2 transparently and returns a discriminated `PayResult`: `ok` splits into `paid: true | false`, so a free 200 is distinguishable from a paid one, and expected failures don't throw.
|
|
394
177
|
|
|
395
|
-
|
|
396
|
-
const { fetch, isLoading, sponsoredRecommendations } = useX402Payment({ wallets });
|
|
397
|
-
|
|
398
|
-
return (
|
|
399
|
-
<div>
|
|
400
|
-
<button onClick={() => fetch(url)} disabled={isLoading}>Pay</button>
|
|
401
|
-
{sponsoredRecommendations?.map((rec, i) => (
|
|
402
|
-
<a key={i} href={rec.resourceUrl}>{rec.sponsor}: {rec.description}</a>
|
|
403
|
-
))}
|
|
404
|
-
</div>
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
```
|
|
178
|
+
Protect an endpoint with `x402Middleware`; the handler runs only after payment settles. In React, `useX402Payment` takes wallets from `@solana/wallet-adapter-react` or `wagmi` and returns a `fetch` that pays automatically. Read a settled receipt off any paid response with `getPaymentReceipt(response)`.
|
|
408
179
|
|
|
409
|
-
|
|
180
|
+
```typescript
|
|
181
|
+
import { x402Middleware } from '@dexterai/x402/server';
|
|
410
182
|
|
|
411
|
-
|
|
183
|
+
app.get('/api/protected',
|
|
184
|
+
x402Middleware({ payTo: 'YourReceivingAddress', amount: '0.01', network: 'eip155:8453' }),
|
|
185
|
+
(req, res) => res.json({ data: 'protected content' }),
|
|
186
|
+
);
|
|
187
|
+
```
|
|
412
188
|
|
|
413
189
|
---
|
|
414
190
|
|
|
415
|
-
##
|
|
191
|
+
## Also in this package
|
|
192
|
+
|
|
193
|
+
Four supporting surfaces, each with its own reference below.
|
|
416
194
|
|
|
417
|
-
|
|
195
|
+
- **Batch settlement (EVM).** Prepay an escrow once, make many discrete paid calls against it with off-chain vouchers, and settle in a handful of transactions to amortize gas. EVM only. `openBatchChannel` / `createBatchSettlementSeller`.
|
|
196
|
+
- **Discovery (bazaar).** Make any `x402Middleware`-protected route discoverable through the official x402 bazaar spec, so agents find it by capability. `bazaarExtension()`.
|
|
197
|
+
- **Sponsored access.** When an agent pays through Dexter's facilitator, a matched recommendation can ride along in the receipt; the agent's model may act on it. `sponsoredAccess: true`.
|
|
198
|
+
- **Auto-listing.** Endpoints paid through the facilitator are auto-discovered, named, and quality-tested, then surfaced in `x402_search` across MCP clients. No registration step.
|
|
418
199
|
|
|
419
|
-
|
|
200
|
+
Full examples for each are in the [reference](#reference) section.
|
|
420
201
|
|
|
421
202
|
---
|
|
422
203
|
|
|
423
204
|
## Supported networks
|
|
424
205
|
|
|
425
|
-
All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supported). USDC on every chain.
|
|
426
|
-
|
|
427
|
-
**Mainnets:**
|
|
206
|
+
All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supported). USDC on every chain. Tabs are Solana; one-shot and batch settlement span Solana and the EVM chains below.
|
|
428
207
|
|
|
429
208
|
| Network | CAIP-2 | Status |
|
|
430
209
|
|---------|--------|--------|
|
|
@@ -437,139 +216,46 @@ All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supp
|
|
|
437
216
|
| BSC | `eip155:56` | Production |
|
|
438
217
|
| SKALE Base | `eip155:1187947933` | Production (zero gas) |
|
|
439
218
|
|
|
440
|
-
|
|
219
|
+
Testnets: Solana Devnet/Testnet, Base Sepolia, SKALE Base Sepolia. Multi-chain endpoints accept any chain in the list; the buyer picks. Pass `network` as an array to `x402Middleware`, with a `payTo` map for per-chain receivers.
|
|
441
220
|
|
|
442
|
-
|
|
443
|
-
|---------|--------|
|
|
444
|
-
| Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` |
|
|
445
|
-
| Solana Testnet | `solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z` |
|
|
446
|
-
| Base Sepolia | `eip155:84532` |
|
|
447
|
-
| SKALE Base Sepolia | `eip155:324705682` |
|
|
221
|
+
---
|
|
448
222
|
|
|
449
|
-
|
|
223
|
+
## Reference
|
|
450
224
|
|
|
451
|
-
|
|
452
|
-
app.get('/api/data', x402Middleware({
|
|
453
|
-
payTo: {
|
|
454
|
-
'solana:*': 'YourSolanaAddress...',
|
|
455
|
-
'eip155:*': '0xYourEvmAddress...',
|
|
456
|
-
},
|
|
457
|
-
amount: '0.01',
|
|
458
|
-
network: [
|
|
459
|
-
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
460
|
-
'eip155:8453',
|
|
461
|
-
'eip155:137',
|
|
462
|
-
'eip155:42161',
|
|
463
|
-
'eip155:10',
|
|
464
|
-
'eip155:43114',
|
|
465
|
-
'eip155:56',
|
|
466
|
-
'eip155:1187947933',
|
|
467
|
-
],
|
|
468
|
-
}));
|
|
469
|
-
```
|
|
225
|
+
### Package exports
|
|
470
226
|
|
|
471
|
-
|
|
227
|
+
```typescript
|
|
228
|
+
// Tabs (Solana): buyer
|
|
229
|
+
import { payUrlWithTab, resolveTabTerms, resolveTabOffer } from '@dexterai/x402/tab';
|
|
472
230
|
|
|
473
|
-
|
|
231
|
+
// Tabs (Solana): seller
|
|
232
|
+
import { tabChallengeMiddleware, tabMiddleware, tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
|
|
474
233
|
|
|
475
|
-
|
|
476
|
-
// Client: canonical entrypoint
|
|
234
|
+
// One-shot client
|
|
477
235
|
import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
|
|
478
236
|
|
|
479
|
-
// Client: sponsored access reader
|
|
480
|
-
import { getSponsoredRecommendations, fireImpressionBeacon } from '@dexterai/x402/client';
|
|
481
|
-
|
|
482
237
|
// React
|
|
483
238
|
import { useX402Payment } from '@dexterai/x402/react';
|
|
484
239
|
|
|
485
|
-
// Server
|
|
486
|
-
import { x402Middleware } from '@dexterai/x402/server';
|
|
487
|
-
|
|
488
|
-
// Server: discovery (bazaar extension)
|
|
489
|
-
import { bazaarExtension, declareDiscoveryExtension } from '@dexterai/x402/server';
|
|
490
|
-
|
|
491
|
-
// Server: manual control
|
|
492
|
-
import { createX402Server } from '@dexterai/x402/server';
|
|
240
|
+
// Server middleware + discovery
|
|
241
|
+
import { x402Middleware, bazaarExtension, declareDiscoveryExtension, createX402Server } from '@dexterai/x402/server';
|
|
493
242
|
|
|
494
243
|
// Batch settlement
|
|
495
244
|
import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
|
|
496
245
|
import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
|
|
497
246
|
|
|
498
|
-
// Adapters (advanced)
|
|
247
|
+
// Adapters (advanced) + utilities
|
|
499
248
|
import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
|
|
500
|
-
|
|
501
|
-
// Utilities
|
|
502
249
|
import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
|
|
503
250
|
```
|
|
504
251
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
## Utilities
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
|
|
511
|
-
|
|
512
|
-
toAtomicUnits(0.05, 6); // '50000'
|
|
513
|
-
toAtomicUnits(1.50, 6); // '1500000'
|
|
514
|
-
fromAtomicUnits('50000', 6); // 0.05
|
|
515
|
-
fromAtomicUnits(1500000n, 6); // 1.5
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## Manual server (advanced)
|
|
521
|
-
|
|
522
|
-
For full control over the payment flow without `x402Middleware`:
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
import { createX402Server } from '@dexterai/x402/server';
|
|
526
|
-
|
|
527
|
-
const server = createX402Server({
|
|
528
|
-
payTo: 'YourAddress...',
|
|
529
|
-
network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
app.post('/protected', async (req, res) => {
|
|
533
|
-
const paymentSig = req.headers['payment-signature'];
|
|
534
|
-
|
|
535
|
-
if (!paymentSig) {
|
|
536
|
-
const requirements = await server.buildRequirements({
|
|
537
|
-
amountAtomic: '50000', // $0.05 USDC
|
|
538
|
-
resourceUrl: req.originalUrl,
|
|
539
|
-
});
|
|
540
|
-
res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
|
|
541
|
-
return res.status(402).json({});
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const result = await server.settlePayment(paymentSig);
|
|
545
|
-
if (!result.success) {
|
|
546
|
-
return res.status(402).json({ error: result.errorReason });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
res.json({ data: 'protected content' });
|
|
550
|
-
});
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
---
|
|
252
|
+
### `payUrlWithTab(url, init, opts) → Promise<{ result, tab }>`
|
|
554
253
|
|
|
555
|
-
|
|
254
|
+
Opens (or reuses) a lock-protected tab to the seller discovered from the URL's `402` challenge, and pays. `opts`: `{ vault, perUnitCap, totalCap, tabs }`. Reuse one `tabs` map across calls to keep a single open tab per seller; `tab.close()` settles everything spent in one transaction.
|
|
556
255
|
|
|
557
|
-
|
|
256
|
+
### `resolveTabTerms(url) → Promise<TabResolution>`
|
|
558
257
|
|
|
559
|
-
|
|
560
|
-
|---|---|
|
|
561
|
-
| `wrapFetch` (`@dexterai/x402/client`) | `payAndFetch` (version-agnostic, discriminated return type) |
|
|
562
|
-
| `createX402Client` (`@dexterai/x402/client`) | `payAndFetch` |
|
|
563
|
-
| `x402AccessPass`, `useAccessPass` | No replacement. Per-request `x402Middleware` + `payAndFetch` covers the same usage pattern. |
|
|
564
|
-
| `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. |
|
|
565
|
-
| `stripePayTo` | No replacement in the SDK. Integrate Stripe at your application layer if needed. |
|
|
566
|
-
| `x402BrowserSupport` | No replacement. Build a custom paywall page if you need one. |
|
|
567
|
-
|
|
568
|
-
None of these will be removed in 3.x.
|
|
569
|
-
|
|
570
|
-
---
|
|
571
|
-
|
|
572
|
-
## API reference
|
|
258
|
+
Reads a URL's tab terms without paying. Returns `{ kind: 'terms', terms: { counterparty, perRequest, network, settlement } }`, or a non-terms kind when the URL offers no tab.
|
|
573
259
|
|
|
574
260
|
### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
|
|
575
261
|
|
|
@@ -580,19 +266,16 @@ None of these will be removed in 3.x.
|
|
|
580
266
|
| `wallets` | `WalletSet` | `{ solana?, evm? }`. The SDK picks the chain by what the merchant accepts and what you can pay |
|
|
581
267
|
| `opts` | `PayAndFetchOptions` | `maxAmountAtomic`, `timeoutMs`, `solanaRpcUrl` |
|
|
582
268
|
|
|
583
|
-
`PayResult` is a discriminated union. Narrow on `ok
|
|
269
|
+
`PayResult` is a discriminated union. Narrow on `ok`, then on `paid`:
|
|
584
270
|
|
|
585
271
|
```typescript
|
|
586
272
|
if (result.ok && result.paid) {
|
|
587
|
-
result.response;
|
|
588
|
-
result.amountPaid; // amount actually paid, in the token's smallest denomination
|
|
589
|
-
result.network; // NetworkRef { caip2, bare, family }
|
|
590
|
-
result.txSignature; // optional; tx hash where the chain reports one
|
|
273
|
+
result.response; result.amountPaid; result.network; result.txSignature;
|
|
591
274
|
} else if (result.ok && !result.paid) {
|
|
592
|
-
result.response; //
|
|
275
|
+
result.response; // merchant didn't demand payment; pass-through
|
|
593
276
|
} else {
|
|
594
277
|
result.reason; // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
|
|
595
|
-
result.detail;
|
|
278
|
+
result.detail;
|
|
596
279
|
}
|
|
597
280
|
```
|
|
598
281
|
|
|
@@ -603,41 +286,31 @@ if (result.ok && result.paid) {
|
|
|
603
286
|
| `payTo` | `string \| { 'solana:*'?, 'eip155:*'?, [caip2]? }` | Yes | Receiver address; map for per-chain receivers |
|
|
604
287
|
| `amount` | `string` | Yes | USD amount, e.g., `'0.01'` |
|
|
605
288
|
| `network` | `string \| string[]` | No | CAIP-2 network(s). Default: Solana mainnet |
|
|
606
|
-
| `
|
|
607
|
-
| `scheme` | `'exact' \| 'batch-settlement'` | No | Use `'batch-settlement'` to mount as a batch-settlement seller |
|
|
289
|
+
| `scheme` | `'exact' \| 'batch-settlement'` | No | `'batch-settlement'` mounts as a batch-settlement seller |
|
|
608
290
|
| `extensions` | `ResourceServerExtension[]` | No | E.g., `[bazaarExtension()]` |
|
|
609
|
-
| `
|
|
610
|
-
| `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Enable Instinct ad-network recommendation injection |
|
|
291
|
+
| `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Instinct ad-network recommendation injection |
|
|
611
292
|
| `facilitatorUrl` | `string` | No | Override facilitator (default: `x402.dexter.cash`) |
|
|
612
|
-
| `verbose` | `boolean` | No | Debug logging |
|
|
613
293
|
|
|
614
|
-
###
|
|
294
|
+
### Batch settlement
|
|
615
295
|
|
|
616
|
-
|
|
296
|
+
```ts
|
|
297
|
+
import { openBatchChannel } from '@dexterai/x402/batch-settlement';
|
|
617
298
|
|
|
618
|
-
|
|
299
|
+
const escrow = await openBatchChannel({ wallet: evmWallet, network: 'eip155:8453', deposit: '0.30' });
|
|
300
|
+
await escrow.fetch('https://api.example.com/v1/data');
|
|
301
|
+
console.log(escrow.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
|
|
302
|
+
await escrow.close();
|
|
303
|
+
```
|
|
619
304
|
|
|
620
|
-
|
|
621
|
-
|---|---|---|
|
|
622
|
-
| `payTo` | `string` | EVM receiver |
|
|
623
|
-
| `network` | `string` | CAIP-2 network |
|
|
624
|
-
| `price` | `string` | Per-call USD price |
|
|
625
|
-
| `storage` | `ChannelStorage` | Optional. Defaults to file storage under `~/.dexter-x402/channels` |
|
|
305
|
+
State auto-persists and resumes with `resumeBatchChannel({ wallet, network, salt })`. If the seller never settles, reclaim unspent escrow with `forceWithdraw()` then `finalizeWithdraw()`. The seller mounts `createBatchSettlementSeller(config)` as an Express handler; Dexter operates the authorizer, so the seller manages no signing key. Returns a handler with `.stop()`, `.closeAll()`, `.closeChannel(id)`.
|
|
626
306
|
|
|
627
|
-
|
|
307
|
+
### Discovery, sponsored access
|
|
628
308
|
|
|
629
|
-
|
|
309
|
+
`bazaarExtension()` plus `declareDiscoveryExtension(config)` attach a spec-compliant `extensions.bazaar` block to a route's 402; extensions are opt-in and failure-isolated, so the payment path is never affected. `sponsoredAccess` injects `_x402_sponsored` into responses; read it with `getSponsoredRecommendations(response)`. Campaign creation is x402-gated at `x402ads.io`.
|
|
630
310
|
|
|
631
|
-
|
|
311
|
+
### Legacy
|
|
632
312
|
|
|
633
|
-
|
|
634
|
-
|---|---|---|
|
|
635
|
-
| `method` | `'GET' \| 'HEAD' \| 'DELETE' \| 'POST' \| 'PUT' \| 'PATCH'` | Optional. If omitted, the actual request method is used. |
|
|
636
|
-
| `queryParams` | `Record<string, ParamSpec>` | For GET/HEAD/DELETE routes |
|
|
637
|
-
| `bodyType` | `'json' \| 'form'` | For POST/PUT/PATCH routes |
|
|
638
|
-
| `body` | `Record<string, ParamSpec>` | For POST/PUT/PATCH routes |
|
|
639
|
-
| `inputSchema` | JSON Schema (Draft 2020-12) | Validates `info` |
|
|
640
|
-
| `output` | `{ example, schema? }` | Example response payload |
|
|
313
|
+
v1-era helpers (`wrapFetch`, `createX402Client`, `x402AccessPass`, `createDynamicPricing`, `stripePayTo`, `x402BrowserSupport`) ship `@deprecated` with JSDoc migration targets and keep working. None will be removed in 3.x. New code should use `payAndFetch` and `x402Middleware`.
|
|
641
314
|
|
|
642
315
|
---
|
|
643
316
|
|
|
@@ -650,8 +323,6 @@ npm run typecheck
|
|
|
650
323
|
npm test # 273 vitest tests
|
|
651
324
|
```
|
|
652
325
|
|
|
653
|
-
---
|
|
654
|
-
|
|
655
326
|
## License
|
|
656
327
|
|
|
657
328
|
MIT. See [LICENSE](./LICENSE).
|