@dcentralab/d402-client 0.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Traia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,594 @@
1
+ # @iatp/d402-client
2
+
3
+ **D402 Payment Protocol Client** for TypeScript/JavaScript
4
+
5
+ Complete TypeScript implementation of the D402 payment protocol for HTTP 402 (Payment Required) responses. Designed for **React/Next.js frontends** to call 402-protected APIs directly from the browser with wallet-based payments.
6
+
7
+ Port of Python IATP package to TypeScript with 1:1 API compatibility.
8
+
9
+ ## ๐Ÿš€ Installation
10
+
11
+ ```bash
12
+ npm install @iatp/d402-client viem wagmi
13
+ # or
14
+ pnpm add @iatp/d402-client viem wagmi
15
+ # or
16
+ yarn add @iatp/d402-client viem wagmi
17
+ ```
18
+
19
+ **Requirements:**
20
+ - React/Next.js app
21
+ - viem ^2.21.0
22
+ - wagmi (for wallet connection)
23
+ - User with Web3 wallet (MetaMask, WalletConnect, etc.)
24
+
25
+ ## ๐Ÿ“– Quick Start (React/Next.js)
26
+
27
+ ### Step 1: Configure Next.js Polyfills
28
+
29
+ **File:** `next.config.js`
30
+
31
+ ```javascript
32
+ module.exports = {
33
+ webpack: (config, { isServer }) => {
34
+ if (!isServer) {
35
+ config.resolve.fallback = {
36
+ ...config.resolve.fallback,
37
+ buffer: require.resolve('buffer/'),
38
+ crypto: require.resolve('crypto-browserify'),
39
+ stream: require.resolve('stream-browserify'),
40
+ process: require.resolve('process/browser')
41
+ }
42
+
43
+ const webpack = require('webpack')
44
+ config.plugins.push(
45
+ new webpack.ProvidePlugin({
46
+ Buffer: ['buffer', 'Buffer'],
47
+ process: 'process/browser'
48
+ })
49
+ )
50
+ }
51
+ return config
52
+ }
53
+ }
54
+ ```
55
+
56
+ **Install polyfills:**
57
+ ```bash
58
+ pnpm add buffer process crypto-browserify stream-browserify
59
+ ```
60
+
61
+ ### Step 2: Use in React Component
62
+
63
+ ```typescript
64
+ 'use client'
65
+
66
+ import { D402Client } from '@iatp/d402-client'
67
+ import { useWalletClient } from 'wagmi'
68
+ import { useState } from 'react'
69
+
70
+ export default function AnalyzeButton() {
71
+ const { data: walletClient } = useWalletClient()
72
+ const [result, setResult] = useState(null)
73
+ const [loading, setLoading] = useState(false)
74
+
75
+ async function analyzeWithPayment() {
76
+ if (!walletClient) {
77
+ alert('Connect your wallet first!')
78
+ return
79
+ }
80
+
81
+ setLoading(true)
82
+
83
+ try {
84
+ // Create D402 client with user's wallet
85
+ const client = new D402Client({
86
+ operatorAccount: walletClient.account,
87
+ walletAddress: '0xUserIATPWallet...', // User's IATPWallet
88
+ maxValue: 1000000n // 1 USDC max
89
+ })
90
+
91
+ // Call 402-protected API directly from browser
92
+ const response = await client.fetch('https://sentiment-api.com/analyze', {
93
+ method: 'POST',
94
+ headers: { 'Content-Type': 'application/json' },
95
+ body: JSON.stringify({ text: 'Bitcoin looks bullish' })
96
+ })
97
+
98
+ const data = await response.json()
99
+ setResult(data)
100
+
101
+ } catch (error) {
102
+ console.error('Payment failed:', error)
103
+ } finally {
104
+ setLoading(false)
105
+ }
106
+ }
107
+
108
+ return (
109
+ <div>
110
+ <button onClick={analyzeWithPayment} disabled={loading || !walletClient}>
111
+ {loading ? 'Processing...' : 'Analyze (0.01 USDC)'}
112
+ </button>
113
+ {result && <pre>{JSON.stringify(result, null, 2)}</pre>}
114
+ </div>
115
+ )
116
+ }
117
+ ```
118
+
119
+ **What happens:**
120
+ 1. User clicks button
121
+ 2. D402Client makes request to external API
122
+ 3. API returns 402
123
+ 4. Client signs payment with user's wallet
124
+ 5. Client retries with payment
125
+ 6. API returns data
126
+ 7. User sees result
127
+
128
+ **All in browser!** No backend needed.
129
+
130
+ ## ๐Ÿ—๏ธ Features
131
+
132
+ ### 1. IATPWallet Creation
133
+
134
+ Create on-chain wallet with owner/operator separation:
135
+
136
+ ```typescript
137
+ import { createIATPWallet } from '@iatp/d402-client'
138
+ import { privateKeyToAccount } from 'viem/accounts'
139
+
140
+ // Owner creates wallet (one-time setup)
141
+ const ownerAccount = privateKeyToAccount('0x...')
142
+
143
+ const result = await createIATPWallet({
144
+ ownerAccount,
145
+ network: 'sepolia',
146
+ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com'
147
+ })
148
+
149
+ console.log('Wallet:', result.walletAddress)
150
+ console.log('Operator key:', result.operatorPrivateKey) // Store securely!
151
+ ```
152
+
153
+ **Returns:**
154
+ - `walletAddress` - IATPWallet contract address
155
+ - `operatorAddress` - Operator's address
156
+ - `operatorPrivateKey` - Operator's private key (store securely!)
157
+ - `transactionHash` - Creation transaction
158
+ - `blockNumber` - Block where wallet was created
159
+
160
+ ### 2. Automatic 402 Payment Handling
161
+
162
+ ```typescript
163
+ import { D402Client } from '@iatp/d402-client'
164
+
165
+ const client = new D402Client({
166
+ operatorAccount,
167
+ walletAddress: '0xYourWallet...',
168
+ maxValue: 1000000n,
169
+ networkFilter: 'base-mainnet', // Only Base network
170
+ schemeFilter: 'exact' // Only exact payments
171
+ })
172
+
173
+ // Client automatically:
174
+ // 1. Detects 402 responses
175
+ // 2. Parses payment requirements
176
+ // 3. Signs with EIP-712
177
+ // 4. Retries with payment
178
+ const response = await client.fetch('https://paid-api.com/endpoint')
179
+ ```
180
+
181
+ ### 3. Manual Payment Flow (Low-Level)
182
+
183
+ ```typescript
184
+ import {
185
+ parsePaymentRequirement,
186
+ signD402Payment,
187
+ encodePayment
188
+ } from '@iatp/d402-client'
189
+
190
+ // 1. Make request
191
+ const response = await fetch('https://api.example.com')
192
+
193
+ if (response.status === 402) {
194
+ // 2. Parse payment requirement
195
+ const requirement = await parsePaymentRequirement(response)
196
+
197
+ // 3. Sign payment
198
+ const signedPayment = await signD402Payment({
199
+ operatorAccount,
200
+ paymentRequirement: requirement,
201
+ walletAddress: '0xYourWallet...'
202
+ })
203
+
204
+ // 4. Encode for header
205
+ const paymentHeader = encodePayment(signedPayment)
206
+
207
+ // 5. Retry with payment
208
+ const paidResponse = await fetch('https://api.example.com', {
209
+ headers: { 'X-Payment': paymentHeader }
210
+ })
211
+ }
212
+ ```
213
+
214
+ ## ๐Ÿ”ง API Reference
215
+
216
+ ### D402Client
217
+
218
+ Main class for automatic 402 payment handling.
219
+
220
+ #### Constructor
221
+
222
+ ```typescript
223
+ new D402Client(config: D402ClientConfig)
224
+ ```
225
+
226
+ **Config Options:**
227
+
228
+ | Option | Type | Required | Description |
229
+ |--------|------|----------|-------------|
230
+ | `operatorAccount` | `Account` | โœ… | Viem account for signing payments (EOA) |
231
+ | `walletAddress` | `0x${string}` | โŒ | IATPWallet contract address (uses operator address if not provided) |
232
+ | `maxValue` | `bigint` | โŒ | Max payment in base units (wei). Safety limit to prevent overpaying. |
233
+ | `networkFilter` | `string` | โŒ | Only select requirements matching this network |
234
+ | `schemeFilter` | `string` | โŒ | Payment scheme filter (default: "exact") |
235
+ | `paymentRequirementsSelector` | `PaymentSelector` | โŒ | Custom payment selection logic |
236
+
237
+ #### Methods
238
+
239
+ **`fetch(url, init?): Promise<Response>`**
240
+
241
+ Fetch with automatic 402 payment handling. Same API as native fetch.
242
+
243
+ ```typescript
244
+ const response = await client.fetch(url, {
245
+ method: 'POST',
246
+ headers: { 'Content-Type': 'application/json' },
247
+ body: JSON.stringify(data)
248
+ })
249
+ ```
250
+
251
+ **`selectPaymentRequirement(requirements): PaymentRequirement`**
252
+
253
+ Select payment requirement from list based on filters.
254
+
255
+ **`getWalletAddress(): 0x${string}`**
256
+
257
+ Get configured wallet address.
258
+
259
+ **`getOperatorAccount(): Account`**
260
+
261
+ Get operator account.
262
+
263
+ **`getMaxValue(): bigint | undefined`**
264
+
265
+ Get maximum payment limit.
266
+
267
+ ### Functions
268
+
269
+ **`createIATPWallet(params): Promise<WalletCreationResult>`**
270
+
271
+ Create new IATPWallet contract on-chain.
272
+
273
+ **Parameters:**
274
+ - `ownerAccount: Account` - Owner's viem account
275
+ - `network?: 'sepolia' | 'localhost'` - Network (default: sepolia)
276
+ - `rpcUrl?: string` - Custom RPC URL
277
+ - `operatorPrivateKey?: 0x${string}` - Use specific operator key
278
+
279
+ **`parsePaymentRequirement(response): Promise<PaymentRequirement>`**
280
+
281
+ Parse first payment requirement from 402 response.
282
+
283
+ **`parseAllPaymentRequirements(response): Promise<PaymentRequirement[]>`**
284
+
285
+ Parse all payment requirements from 402 response.
286
+
287
+ **`signD402Payment(params): Promise<SignedPayment>`**
288
+
289
+ Sign payment with EIP-712.
290
+
291
+ **`encodePayment(payment): string`**
292
+
293
+ Encode signed payment to base64 for X-Payment header.
294
+
295
+ **`decodePayment(encoded): SignedPayment`**
296
+
297
+ Decode base64 payment header.
298
+
299
+ ### Contract Functions
300
+
301
+ **`getContractAddress(name, network): string | null`**
302
+
303
+ Get contract address for network.
304
+
305
+ **`getContractAbi(name, network): any[] | null`**
306
+
307
+ Get contract ABI for network.
308
+
309
+ **`getContractConfig(name, network): { address, abi } | null`**
310
+
311
+ Get both address and ABI.
312
+
313
+ ### Utility Functions
314
+
315
+ **`parseMoney(amount, decimals): bigint`**
316
+
317
+ Convert money string to atomic units.
318
+
319
+ **`usdToUsdc(amount): bigint`**
320
+
321
+ Convert USD to USDC wei (6 decimals).
322
+
323
+ **`generateNonce(): 0x${string}`**
324
+
325
+ Generate random 32-byte nonce.
326
+
327
+ **`getChainId(network): number`**
328
+
329
+ Get chain ID for network name.
330
+
331
+ ## ๐Ÿ’ก Usage Patterns
332
+
333
+ ### Pattern 1: Frontend Direct (Recommended for Web3 Apps)
334
+
335
+ **React component calls 402 API directly:**
336
+
337
+ ```typescript
338
+ 'use client'
339
+
340
+ import { D402Client } from '@iatp/d402-client'
341
+ import { useWalletClient } from 'wagmi'
342
+
343
+ export default function PaymentComponent() {
344
+ const { data: walletClient } = useWalletClient()
345
+
346
+ async function callPaidAPI() {
347
+ if (!walletClient) return
348
+
349
+ // User's wallet signs payments
350
+ const client = new D402Client({
351
+ operatorAccount: walletClient.account,
352
+ walletAddress: '0xUserIATPWallet...',
353
+ maxValue: 1000000n
354
+ })
355
+
356
+ // Calls external 402 API directly
357
+ const response = await client.fetch('https://sentiment-api.com/analyze', {
358
+ method: 'POST',
359
+ body: JSON.stringify({ text: 'Bitcoin sentiment' })
360
+ })
361
+
362
+ return await response.json()
363
+ }
364
+ }
365
+ ```
366
+
367
+ **Flow:** Browser โ†’ D402Client โ†’ External 402 API
368
+
369
+ **Pros:**
370
+ - โœ… User pays from their wallet
371
+ - โœ… Fully decentralized
372
+ - โœ… No backend needed
373
+
374
+ **Cons:**
375
+ - โš ๏ธ Requires wallet connection
376
+ - โš ๏ธ Needs Next.js polyfill config
377
+ - โš ๏ธ User approves each payment
378
+
379
+ ### Pattern 2: Backend Proxy (Alternative)
380
+
381
+ **Backend handles 402 calls, frontend just calls your API:**
382
+
383
+ **Backend (Node.js):**
384
+ ```typescript
385
+ import { D402Client } from '@iatp/d402-client'
386
+ import { privateKeyToAccount } from 'viem/accounts'
387
+
388
+ const operatorAccount = privateKeyToAccount(process.env.OPERATOR_KEY!)
389
+
390
+ app.post('/api/analyze', async (req, res) => {
391
+ const client = new D402Client({
392
+ operatorAccount,
393
+ walletAddress: req.user.iatpWallet,
394
+ maxValue: 1000000n
395
+ })
396
+
397
+ const response = await client.fetch('https://sentiment-api.com/analyze', {
398
+ method: 'POST',
399
+ body: JSON.stringify(req.body)
400
+ })
401
+
402
+ res.json(await response.json())
403
+ })
404
+ ```
405
+
406
+ **Frontend (simpler):**
407
+ ```typescript
408
+ // Just call YOUR backend
409
+ const response = await fetch('/api/analyze', {
410
+ method: 'POST',
411
+ body: JSON.stringify({ text: 'Analyze this' })
412
+ })
413
+ ```
414
+
415
+ **Flow:** Browser โ†’ Your Backend โ†’ D402Client โ†’ External 402 API
416
+
417
+ **Pros:**
418
+ - โœ… Simpler frontend (no wallet needed)
419
+ - โœ… No polyfills needed in frontend
420
+ - โœ… Smooth UX (no wallet popups)
421
+
422
+ **Cons:**
423
+ - โš ๏ธ Backend controls payments
424
+ - โš ๏ธ More centralized
425
+
426
+ ## ๐ŸŒ Supported Networks
427
+
428
+ | Network | Chain ID | USDC Address |
429
+ |---------|----------|--------------|
430
+ | Ethereum Mainnet | 1 | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
431
+ | Sepolia Testnet | 11155111 | `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238` |
432
+ | Base | 8453 | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
433
+ | Base Sepolia | 84532 | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |
434
+ | Polygon | 137 | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
435
+ | Arbitrum One | 42161 | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` |
436
+ | Arbitrum Sepolia | 421614 | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` |
437
+
438
+ ## ๐Ÿ“ฆ Package Exports
439
+
440
+ ```typescript
441
+ // Main client
442
+ export { D402Client } from '@iatp/d402-client'
443
+
444
+ // Wallet management
445
+ export { createIATPWallet } from '@iatp/d402-client'
446
+
447
+ // Payment functions
448
+ export {
449
+ parsePaymentRequirement,
450
+ parseAllPaymentRequirements,
451
+ signD402Payment,
452
+ encodePayment,
453
+ decodePayment
454
+ } from '@iatp/d402-client'
455
+
456
+ // Contract access
457
+ export {
458
+ ContractName,
459
+ getContractAddress,
460
+ getContractAbi,
461
+ getContractConfig
462
+ } from '@iatp/d402-client'
463
+
464
+ // Utilities
465
+ export {
466
+ parseMoney,
467
+ usdToUsdc,
468
+ generateNonce,
469
+ getChainId
470
+ } from '@iatp/d402-client'
471
+
472
+ // Error classes
473
+ export {
474
+ PaymentError,
475
+ PaymentAmountExceededError,
476
+ UnsupportedSchemeError,
477
+ Invalid402ResponseError
478
+ } from '@iatp/d402-client'
479
+
480
+ // Types
481
+ export type {
482
+ D402ClientConfig,
483
+ PaymentRequirement,
484
+ SignedPayment,
485
+ WalletCreationResult
486
+ } from '@iatp/d402-client'
487
+ ```
488
+
489
+ ## ๐Ÿงช Testing
490
+
491
+ ```bash
492
+ # Run all tests
493
+ pnpm test
494
+
495
+ # Run specific test file
496
+ pnpm test client.test.ts
497
+
498
+ # Run with coverage
499
+ pnpm test --coverage
500
+
501
+ # Watch mode
502
+ pnpm test --watch
503
+ ```
504
+
505
+ **Test Coverage:**
506
+ - โœ… 17 encoder/decoder tests
507
+ - โœ… 19 parser tests
508
+ - โœ… 29 contracts tests
509
+ - โœ… 24 signer tests
510
+ - โœ… 14 wallet tests
511
+ - โœ… 19 client constructor tests
512
+ - โœ… 22 integration tests
513
+
514
+ **Total: 144 tests**
515
+
516
+ ## ๐Ÿ”’ Security
517
+
518
+ ### Private Key Handling
519
+
520
+ **โœ… Safe (Backend):**
521
+ ```typescript
522
+ // Store operator key in environment variable
523
+ const operatorAccount = privateKeyToAccount(process.env.OPERATOR_KEY!)
524
+ ```
525
+
526
+ **โŒ Unsafe (Frontend):**
527
+ ```typescript
528
+ // Never hardcode private keys in frontend code
529
+ const account = privateKeyToAccount('0xHARDCODED') // DON'T DO THIS!
530
+ ```
531
+
532
+ **โœ… Safe (Frontend with Wallet):**
533
+ ```typescript
534
+ // Use wallet client from wagmi/viem
535
+ const { data: walletClient } = useWalletClient()
536
+ const client = new D402Client({
537
+ operatorAccount: walletClient.account
538
+ })
539
+ ```
540
+
541
+ ### Payment Security
542
+
543
+ - โœ… EIP-712 typed signatures prevent tampering
544
+ - โœ… Nonce prevents replay attacks
545
+ - โœ… Timestamp bounds (validAfter/validBefore)
546
+ - โœ… Amount verification
547
+ - โœ… Destination address verification
548
+ - โœ… maxValue safety limit
549
+
550
+ ## ๐Ÿ› ๏ธ Development
551
+
552
+ ### Build
553
+
554
+ ```bash
555
+ pnpm build
556
+ ```
557
+
558
+ Outputs:
559
+ - `dist/index.js` - CommonJS
560
+ - `dist/index.mjs` - ES Module
561
+ - `dist/index.d.ts` - TypeScript types
562
+
563
+ ### Type Checking
564
+
565
+ ```bash
566
+ pnpm typecheck
567
+ ```
568
+
569
+ ## ๐Ÿ“ Related Packages
570
+
571
+ Part of the IATP-JS monorepo:
572
+ - `@iatp/d402-client` - D402 payment protocol (this package)
573
+ - `@iatp/mcp-client` - MCP integration (planned)
574
+ - `@iatp/a2a-client` - A2A protocol (planned)
575
+
576
+ ## ๐Ÿ”— Related Projects
577
+
578
+ - [IATP Python](https://github.com/Traia-IO/IATP) - Python implementation
579
+ - [Viem](https://viem.sh) - TypeScript Web3 library
580
+ - [D402 Protocol](https://docs.cdp.coinbase.com/x402) - HTTP 402 specification
581
+
582
+ ## ๐Ÿ“„ License
583
+
584
+ MIT
585
+
586
+ ## ๐Ÿ’ฌ Support
587
+
588
+ - GitHub Issues: [iatp-js/issues](https://github.com/Traia-IO/iatp-js/issues)
589
+ - Email: support@traia.io
590
+
591
+ ---
592
+
593
+ **Made with โค๏ธ by the Traia Team**
594
+