@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.
- package/README.md +313 -702
- package/dist/adapters/index.cjs +1 -1
- package/dist/adapters/index.d.cts +3 -3
- package/dist/adapters/index.d.ts +3 -3
- package/dist/adapters/index.js +1 -1
- package/dist/batch-settlement/index.d.cts +3 -3
- package/dist/batch-settlement/index.d.ts +3 -3
- package/dist/batch-settlement/seller/index.d.cts +4 -4
- package/dist/batch-settlement/seller/index.d.ts +4 -4
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +308 -238
- package/dist/client/index.d.ts +308 -238
- package/dist/client/index.js +1 -1
- package/dist/{constants-qU-4U3L-.d.cts → constants-D41hDAG6.d.cts} +13 -1
- package/dist/{constants-qU-4U3L-.d.ts → constants-D41hDAG6.d.ts} +13 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.cts +12 -4
- package/dist/react/index.d.ts +12 -4
- package/dist/react/index.js +1 -1
- package/dist/server/index.cjs +3 -3
- package/dist/server/index.d.cts +63 -5
- package/dist/server/index.d.ts +63 -5
- package/dist/server/index.js +3 -3
- package/dist/tab/adapters/solana/index.cjs +1 -0
- package/dist/tab/adapters/solana/index.d.cts +123 -0
- package/dist/tab/adapters/solana/index.d.ts +123 -0
- package/dist/tab/adapters/solana/index.js +1 -0
- package/dist/tab/index.cjs +6 -0
- package/dist/tab/index.d.cts +29 -0
- package/dist/tab/index.d.ts +29 -0
- package/dist/tab/index.js +6 -0
- package/dist/tab/seller/index.cjs +6 -0
- package/dist/tab/seller/index.d.cts +291 -0
- package/dist/tab/seller/index.d.ts +291 -0
- package/dist/tab/seller/index.js +6 -0
- package/dist/{types-XG8QvfyL.d.cts → types-C8lyIOmX.d.cts} +1 -1
- package/dist/{types-DllrEG_Z.d.ts → types-CTl7yVq6.d.ts} +1 -1
- package/dist/{types-pOwQlGEV.d.cts → types-CiPcPs0w.d.cts} +1 -1
- package/dist/{types-DDBPREEu.d.ts → types-D9VMq7In.d.ts} +1 -1
- package/dist/types-DIrmhiD-.d.cts +234 -0
- package/dist/types-DIrmhiD-.d.ts +234 -0
- package/dist/{types-htvWHuW3.d.cts → types-RxdlGPaG.d.cts} +122 -1
- package/dist/{types-htvWHuW3.d.ts → types-RxdlGPaG.d.ts} +122 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.d.cts +10 -1
- package/dist/utils/index.d.ts +10 -1
- package/dist/utils/index.js +1 -1
- package/dist/{sponsored-access-D96FgkQK.d.ts → x402-client-CHrU2Bs6.d.cts} +114 -63
- package/dist/{sponsored-access-D7H-womP.d.cts → x402-client-CzseAnIt.d.ts} +114 -63
- 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>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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 {
|
|
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
|
-
|
|
91
|
-
const
|
|
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
|
|
106
|
-
|
|
49
|
+
const result = await payAndFetch(
|
|
50
|
+
'https://api.example.com/protected',
|
|
51
|
+
{ method: 'GET' },
|
|
52
|
+
{ solana, evm },
|
|
53
|
+
{},
|
|
54
|
+
);
|
|
107
55
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
import { createX402Client } from '@dexterai/x402/client';
|
|
69
|
+
### Pay for a resource (Browser, React)
|
|
118
70
|
|
|
119
|
-
|
|
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';
|
|
148
|
-
import { useAccount } from 'wagmi';
|
|
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
|
-
|
|
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
|
|
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
|
|
188
|
-
|
|
189
|
-
(
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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',
|
|
278
|
-
price: '0.08',
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
returns the same callable seller object, so you still get a `.stop()` /
|
|
297
|
-
`.closeAll()` / `.closeChannel()` handle:
|
|
214
|
+
## Discovery (bazaar extension)
|
|
298
215
|
|
|
299
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
397
|
+
## Package exports
|
|
382
398
|
|
|
383
399
|
```typescript
|
|
384
|
-
// Client
|
|
385
|
-
import {
|
|
400
|
+
// Client: canonical entrypoint
|
|
401
|
+
import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
|
|
386
402
|
|
|
387
|
-
// Client
|
|
388
|
-
import {
|
|
403
|
+
// Client: sponsored access reader
|
|
404
|
+
import { getSponsoredRecommendations, fireImpressionBeacon } from '@dexterai/x402/client';
|
|
389
405
|
|
|
390
|
-
//
|
|
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
|
|
409
|
+
// Server: middleware
|
|
400
410
|
import { x402Middleware } from '@dexterai/x402/server';
|
|
401
411
|
|
|
402
|
-
// Server
|
|
403
|
-
import {
|
|
412
|
+
// Server: discovery (bazaar extension)
|
|
413
|
+
import { bazaarExtension, declareDiscoveryExtension } from '@dexterai/x402/server';
|
|
404
414
|
|
|
405
|
-
// Server
|
|
415
|
+
// Server: manual control
|
|
406
416
|
import { createX402Server } from '@dexterai/x402/server';
|
|
407
417
|
|
|
408
|
-
//
|
|
409
|
-
import {
|
|
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
|
-
//
|
|
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
|
-
|
|
429
|
-
toAtomicUnits(
|
|
430
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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: '
|
|
473
|
+
res.json({ data: 'protected content' });
|
|
592
474
|
});
|
|
593
475
|
```
|
|
594
476
|
|
|
595
477
|
---
|
|
596
478
|
|
|
597
|
-
##
|
|
479
|
+
## Legacy capabilities
|
|
598
480
|
|
|
599
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
656
|
-
import { createX402Server, createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';
|
|
498
|
+
### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
|
|
657
499
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
//
|
|
716
|
-
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
//
|
|
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
|
-
###
|
|
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
|
-
| `
|
|
940
|
-
| `
|
|
941
|
-
| `
|
|
942
|
-
| `
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
|
947
|
-
|
|
948
|
-
| `
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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 #
|
|
571
|
+
npm run build # ESM + CJS
|
|
961
572
|
npm run dev # Watch mode
|
|
962
|
-
npm run typecheck
|
|
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
|
|
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
|
|
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>
|