@dexterai/x402 1.0.3 โ†’ 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,135 +2,91 @@
2
2
  <img src="./assets/dexter-wordmark.svg" alt="Dexter" width="360">
3
3
  </p>
4
4
 
5
- # @dexterai/x402
5
+ <h1 align="center">@dexterai/x402</h1>
6
6
 
7
7
  <p align="center">
8
- <a href="https://nodejs.org/en/download"><img src="https://img.shields.io/badge/node-%3E=18-green.svg" alt="Node >= 18"></a>
9
- <a href="https://www.npmjs.com/package/@dexterai/x402"><img src="https://img.shields.io/npm/v/@dexterai/x402.svg" alt="npm version"></a>
10
- <a href="https://x402.dexter.cash"><img src="https://img.shields.io/badge/facilitator-x402.dexter.cash-orange.svg" alt="x402 Facilitator"></a>
8
+ <strong>The x402 SDK that actually works.</strong>
11
9
  </p>
12
10
 
13
- Chain-agnostic SDK for x402 v2 payments. Works with **Solana**, **Base**, and any x402-compatible network.
11
+ <p align="center">
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
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E=18-brightgreen.svg" alt="Node"></a>
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
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://dexter.cash/sdk"><strong>๐Ÿ‘‰ Try it with real payments โ†’</strong></a>
19
+ </p>
14
20
 
15
21
  ---
16
22
 
17
- ## Highlights
23
+ ## โœจ Why This SDK?
18
24
 
19
- - **Chain-agnostic** โ€“ pay on Solana, Base, or any supported chain
20
- - **Zero-config client** โ€“ wrap `fetch`, auto-handles 402 responses
21
- - **Server helpers** โ€“ generate requirements, verify & settle payments
22
- - **React hook** โ€“ multi-wallet support with balance tracking
23
- - **Dual ESM/CJS** โ€“ full TypeScript definitions
25
+ - **๐Ÿ”— Multi-chain** โ€” Solana and Base, same API
26
+ - **โšก x402 v2** โ€” Full protocol support, verified working
27
+ - **โš›๏ธ React Hook** โ€” `useX402Payment` with loading states, balances, and transaction tracking
28
+ - **๐Ÿ’ฐ Smart Balance Check** โ€” Clear "insufficient funds" error *before* the wallet popup
29
+ - **๐Ÿ‘ป Phantom Compatible** โ€” Handles Lighthouse safety assertions automatically
30
+ - **๐Ÿ“ฆ Zero Config** โ€” Wrap `fetch()`, payments just work
24
31
 
25
32
  ---
26
33
 
27
- ## Quick Start
34
+ ## ๐ŸŽฎ See It Working
35
+
36
+ **Don't take our word for it.** Make a real payment yourself:
37
+
38
+ **[โ†’ dexter.cash/sdk](https://dexter.cash/sdk)**
39
+
40
+ The demo uses this exact SDK. Solana and Base. Real USDC. Real transactions.
41
+
42
+ ---
43
+
44
+ ## ๐Ÿš€ Quick Start
28
45
 
29
46
  ### Install
30
47
 
31
48
  ```bash
32
- npm install @dexterai/x402 @solana/web3.js @solana/spl-token
49
+ npm install @dexterai/x402
33
50
  ```
34
51
 
35
- ### Client (Browser/Node)
52
+ ### Client (Browser)
36
53
 
37
54
  ```typescript
38
55
  import { createX402Client } from '@dexterai/x402/client';
39
56
 
40
- // Single wallet (Solana)
41
- const client = createX402Client({
42
- wallet: solanaWallet,
43
- });
44
-
45
- // Multi-chain: provide wallets for each chain
46
57
  const client = createX402Client({
47
58
  wallets: {
48
59
  solana: solanaWallet,
49
- evm: evmWallet, // from wagmi, viem, etc.
60
+ evm: evmWallet,
61
+ },
62
+ rpcUrls: {
63
+ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'https://your-solana-rpc.com',
64
+ 'eip155:8453': 'https://your-base-rpc.com',
50
65
  },
51
- preferredNetwork: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
52
66
  });
53
67
 
54
- // Automatically handles 402 responses
68
+ // That's it. 402 responses are handled automatically.
55
69
  const response = await client.fetch('https://api.example.com/protected');
56
70
  ```
57
71
 
58
- ### Server (Express/Next.js)
59
-
60
- ```typescript
61
- import { createX402Server } from '@dexterai/x402/server';
62
-
63
- // Create server for Solana payments
64
- const server = createX402Server({
65
- payTo: 'YourSolanaAddress...',
66
- network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
67
- });
68
-
69
- // Or for Base payments
70
- const baseServer = createX402Server({
71
- payTo: '0xYourEvmAddress...',
72
- network: 'eip155:8453',
73
- asset: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 },
74
- });
75
-
76
- // In your route handler
77
- app.post('/protected', async (req, res) => {
78
- const paymentSig = req.headers['payment-signature'];
79
-
80
- if (!paymentSig) {
81
- const requirements = await server.buildRequirements({
82
- amountAtomic: '50000', // 0.05 USDC
83
- resourceUrl: req.originalUrl,
84
- });
85
- res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
86
- return res.status(402).json({});
87
- }
88
-
89
- const result = await server.settlePayment(paymentSig);
90
- if (!result.success) {
91
- return res.status(402).json({ error: result.errorReason });
92
- }
93
-
94
- res.json({ data: 'protected content', transaction: result.transaction });
95
- });
96
- ```
97
-
98
- > โš ๏ธ **Note:** The server SDK (`createX402Server`) has not been battle-tested in production.
99
- > The client SDK and React hook have been verified with real payments.
100
-
101
72
  ### React
102
73
 
103
74
  ```tsx
104
75
  import { useX402Payment } from '@dexterai/x402/react';
105
- import { useWallet } from '@solana/wallet-adapter-react';
106
- import { useAccount } from 'wagmi';
107
76
 
108
77
  function PayButton() {
109
- const solanaWallet = useWallet();
110
- const evmWallet = useAccount();
111
-
112
- const {
113
- fetch,
114
- isLoading,
115
- balances,
116
- connectedChains,
117
- transactionUrl,
118
- } = useX402Payment({
119
- wallets: {
120
- solana: solanaWallet,
121
- evm: evmWallet,
122
- },
78
+ const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
79
+ wallets: { solana: solanaWallet, evm: evmWallet },
80
+ rpcUrls: { /* your RPC endpoints */ },
123
81
  });
124
82
 
125
83
  return (
126
84
  <div>
127
- {balances.map(b => (
128
- <p key={b.network}>{b.chainName}: ${b.balance.toFixed(2)}</p>
129
- ))}
85
+ <p>Balance: ${balances[0]?.balance.toFixed(2)}</p>
130
86
  <button onClick={() => fetch(url)} disabled={isLoading}>
131
- {isLoading ? 'Paying...' : 'Pay $0.05'}
87
+ {isLoading ? 'Paying...' : 'Pay'}
132
88
  </button>
133
- {transactionUrl && <a href={transactionUrl}>View Transaction</a>}
89
+ {transactionUrl && <a href={transactionUrl}>View Transaction โ†—</a>}
134
90
  </div>
135
91
  );
136
92
  }
@@ -138,120 +94,189 @@ function PayButton() {
138
94
 
139
95
  ---
140
96
 
141
- ## Supported Networks
97
+ ## ๐ŸŒ Supported Networks
142
98
 
143
- | Network | CAIP-2 ID | Asset |
144
- |---------|-----------|-------|
145
- | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | USDC |
146
- | Base Mainnet | `eip155:8453` | USDC |
147
- | Arbitrum One | `eip155:42161` | USDC |
148
- | Ethereum | `eip155:1` | USDC |
99
+ | Network | Identifier | Status |
100
+ |---------|------------|--------|
101
+ | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | โœ… Verified |
102
+ | Base Mainnet | `eip155:8453` | โœ… Verified |
149
103
 
150
- ---
104
+ All networks use USDC.
151
105
 
152
- ## API
106
+ ---
153
107
 
154
- ### Client
108
+ ## ๐Ÿ“ฆ Package Exports
155
109
 
156
110
  ```typescript
111
+ // Client - browser & Node.js
157
112
  import { createX402Client } from '@dexterai/x402/client';
158
113
 
159
- const client = createX402Client({
160
- wallets: { solana, evm }, // Multi-chain wallets
161
- wallet: solanaWallet, // Legacy: single wallet
162
- preferredNetwork: '...', // Prefer this network
163
- rpcUrls: { 'eip155:8453': 'https://...' }, // Custom RPCs
164
- maxAmountAtomic: '100000', // Payment cap
165
- verbose: true, // Debug logging
166
- });
114
+ // React hook
115
+ import { useX402Payment } from '@dexterai/x402/react';
116
+
117
+ // Server helpers (see note below)
118
+ import { createX402Server } from '@dexterai/x402/server';
119
+
120
+ // Chain adapters (advanced)
121
+ import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
122
+
123
+ // Utilities
124
+ import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
125
+ ```
126
+
127
+ ---
128
+
129
+ ## ๐Ÿ› ๏ธ Utilities
130
+
131
+ ```typescript
132
+ import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
167
133
 
168
- const response = await client.fetch(url, init);
134
+ // Convert dollars to atomic units (for API calls)
135
+ toAtomicUnits(0.05, 6); // '50000'
136
+ toAtomicUnits(1.50, 6); // '1500000'
137
+
138
+ // Convert atomic units back to dollars (for display)
139
+ fromAtomicUnits('50000', 6); // 0.05
140
+ fromAtomicUnits(1500000n, 6); // 1.5
169
141
  ```
170
142
 
171
- ### Server
143
+ ---
144
+
145
+ ## ๐Ÿ–ฅ๏ธ Server SDK
172
146
 
173
147
  ```typescript
174
148
  import { createX402Server } from '@dexterai/x402/server';
175
149
 
176
150
  const server = createX402Server({
177
- payTo: 'address',
151
+ payTo: 'YourAddress...',
178
152
  network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
179
- asset: { address: 'mint', decimals: 6 },
180
153
  facilitatorUrl: 'https://x402.dexter.cash',
181
- defaultTimeoutSeconds: 60,
182
154
  });
183
155
 
184
- await server.buildRequirements({ amountAtomic, resourceUrl });
185
- server.encodeRequirements(requirements);
186
- await server.verifyPayment(header);
187
- await server.settlePayment(header);
188
- ```
156
+ // In your route handler
157
+ app.post('/protected', async (req, res) => {
158
+ const paymentSig = req.headers['payment-signature'];
189
159
 
190
- ### React Hook
160
+ if (!paymentSig) {
161
+ const requirements = await server.buildRequirements({
162
+ amountAtomic: '50000', // $0.05 USDC
163
+ resourceUrl: req.originalUrl,
164
+ });
165
+ res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
166
+ return res.status(402).json({});
167
+ }
191
168
 
192
- ```typescript
193
- import { useX402Payment } from '@dexterai/x402/react';
169
+ const result = await server.settlePayment(paymentSig);
170
+ if (!result.success) {
171
+ return res.status(402).json({ error: result.errorReason });
172
+ }
194
173
 
195
- const {
196
- fetch, // Payment-aware fetch
197
- isLoading, // Payment in progress
198
- status, // 'idle' | 'pending' | 'success' | 'error'
199
- error, // Error if failed
200
- transactionId, // Tx signature on success
201
- transactionUrl, // Explorer link
202
- balances, // Token balances per chain
203
- connectedChains, // { solana: bool, evm: bool }
204
- reset, // Clear state
205
- refreshBalances, // Manual balance refresh
206
- } = useX402Payment({ wallets, preferredNetwork, verbose });
174
+ res.json({ data: 'Your protected content' });
175
+ });
207
176
  ```
208
177
 
209
- ### Adapters (Advanced)
178
+ > โš ๏ธ **Note:** The server SDK has not been battle-tested in production yet. The client SDK and React hook have been verified with real payments at [dexter.cash/sdk](https://dexter.cash/sdk).
179
+
180
+ ---
181
+
182
+ ## ๐Ÿ’ธ Dynamic Pricing
183
+
184
+ For LLM/AI endpoints where cost scales with input size:
210
185
 
211
186
  ```typescript
212
- import {
213
- createSolanaAdapter,
214
- createEvmAdapter,
215
- SOLANA_MAINNET,
216
- BASE_MAINNET,
217
- } from '@dexterai/x402/adapters';
218
-
219
- const adapters = [
220
- createSolanaAdapter({ verbose: true }),
221
- createEvmAdapter({ rpcUrls: { 'eip155:8453': '...' } }),
222
- ];
223
-
224
- // Find adapter for a network
225
- const adapter = adapters.find(a => a.canHandle('eip155:8453'));
226
-
227
- // Build transaction manually
228
- const signedTx = await adapter.buildTransaction(accept, wallet);
229
-
230
- // Check balance
231
- const balance = await adapter.getBalance(accept, wallet);
187
+ import { createX402Server, createDynamicPricing } from '@dexterai/x402/server';
188
+
189
+ const server = createX402Server({ payTo: '...', network: '...' });
190
+ const pricing = createDynamicPricing({
191
+ unitSize: 1000, // chars per unit
192
+ ratePerUnit: 0.01, // $0.01 per unit
193
+ minUsd: 0.01, // floor
194
+ maxUsd: 10.00, // ceiling
195
+ });
196
+
197
+ app.post('/api/llm', async (req, res) => {
198
+ const { prompt } = req.body;
199
+ const paymentSig = req.headers['payment-signature'];
200
+
201
+ if (!paymentSig) {
202
+ const quote = pricing.calculate(prompt);
203
+ const requirements = await server.buildRequirements({
204
+ amountAtomic: quote.amountAtomic,
205
+ resourceUrl: req.originalUrl,
206
+ });
207
+ res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
208
+ res.setHeader('X-Quote-Hash', quote.quoteHash);
209
+ return res.status(402).json({ usdAmount: quote.usdAmount });
210
+ }
211
+
212
+ // Validate quote hasn't changed (prevents prompt manipulation)
213
+ const quoteHash = req.headers['x-quote-hash'];
214
+ if (!pricing.validateQuote(prompt, quoteHash)) {
215
+ return res.status(400).json({ error: 'Prompt changed, re-quote required' });
216
+ }
217
+
218
+ const result = await server.settlePayment(paymentSig);
219
+ if (!result.success) return res.status(402).json({ error: result.errorReason });
220
+
221
+ const response = await runLLM(prompt);
222
+ res.json(response);
223
+ });
232
224
  ```
233
225
 
226
+ The client SDK automatically forwards `X-Quote-Hash` on retry.
227
+
228
+ ---
229
+
230
+ ## ๐Ÿ“‹ API Reference
231
+
232
+ ### `createX402Client(options)`
233
+
234
+ | Option | Type | Description |
235
+ |--------|------|-------------|
236
+ | `wallet` | `SolanaWallet` | Single Solana wallet (legacy) |
237
+ | `wallets` | `{ solana?, evm? }` | Multi-chain wallets |
238
+ | `preferredNetwork` | `string` | Prefer this network when multiple options available |
239
+ | `rpcUrls` | `Record<string, string>` | RPC endpoints per network (CAIP-2 format) |
240
+ | `maxAmountAtomic` | `string` | Maximum payment cap |
241
+ | `verbose` | `boolean` | Enable debug logging |
242
+
243
+ ### `useX402Payment(options)`
244
+
245
+ Returns:
246
+
247
+ | Property | Type | Description |
248
+ |----------|------|-------------|
249
+ | `fetch` | `function` | Payment-aware fetch |
250
+ | `isLoading` | `boolean` | Payment in progress |
251
+ | `status` | `string` | `'idle'` \| `'pending'` \| `'success'` \| `'error'` |
252
+ | `error` | `X402Error?` | Error details if failed |
253
+ | `transactionId` | `string?` | Transaction signature |
254
+ | `transactionUrl` | `string?` | Block explorer link |
255
+ | `balances` | `Balance[]` | Token balances per chain |
256
+ | `refreshBalances` | `function` | Manual refresh |
257
+ | `reset` | `function` | Clear state |
258
+
234
259
  ---
235
260
 
236
- ## Development
261
+ ## ๐Ÿ”ง Development
237
262
 
238
263
  ```bash
239
264
  npm run build # Build ESM + CJS
240
265
  npm run dev # Watch mode
241
- npm run typecheck # TypeScript checks
266
+ npm run typecheck # TypeScript
242
267
  npm test # Run tests
243
268
  ```
244
269
 
245
270
  ---
246
271
 
247
- ## Resources
272
+ ## ๐Ÿ“„ License
248
273
 
249
- - [Dexter Facilitator](https://x402.dexter.cash)
250
- - [x402 Protocol Spec](https://docs.cdp.coinbase.com/x402)
251
- - [Seller Onboarding](https://dexter.cash/onboard)
274
+ MIT โ€” see [LICENSE](./LICENSE)
252
275
 
253
276
  ---
254
277
 
255
- ## License
256
-
257
- MIT โ€“ see [LICENSE](./LICENSE)
278
+ <p align="center">
279
+ <a href="https://x402.dexter.cash">Dexter Facilitator</a> ยท
280
+ <a href="https://dexter.cash/sdk">Live Demo</a> ยท
281
+ <a href="https://dexter.cash/onboard">Become a Seller</a>
282
+ </p>
@@ -515,6 +515,10 @@ function createX402Client(config) {
515
515
  );
516
516
  }
517
517
  log("Payment requirements:", requirements);
518
+ const quoteHash = response.headers.get("X-Quote-Hash");
519
+ if (quoteHash) {
520
+ log("Quote hash received:", quoteHash);
521
+ }
518
522
  const match = findPaymentOption(requirements.accepts);
519
523
  if (!match) {
520
524
  const availableNetworks = requirements.accepts.map((a) => a.network).join(", ");
@@ -576,7 +580,9 @@ function createX402Client(config) {
576
580
  ...init,
577
581
  headers: {
578
582
  ...init?.headers || {},
579
- "PAYMENT-SIGNATURE": paymentSignatureHeader
583
+ "PAYMENT-SIGNATURE": paymentSignatureHeader,
584
+ // Forward quote hash for dynamic pricing validation
585
+ ...quoteHash ? { "X-Quote-Hash": quoteHash } : {}
580
586
  }
581
587
  });
582
588
  log("Retry response status:", retryResponse.status);